mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
200ddbfa28 | ||
|
|
f34418da6d | ||
|
|
7ff86c0ee3 | ||
|
|
5a58f44548 | ||
|
|
d27d6db1ea | ||
|
|
c75cb5d7ce | ||
|
|
2207147e7f | ||
|
|
d2bb3813d9 | ||
|
|
cac73e6d45 | ||
|
|
b5604eab11 | ||
|
|
b498882708 | ||
|
|
4712ceaf2c | ||
|
|
381d71663c | ||
|
|
f7f4135193 | ||
|
|
dbf6d9e5fe |
@@ -28,7 +28,7 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := containerService.Page(req)
|
||||
total, list, err := containerService.Page(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -249,9 +249,10 @@ func (b *BaseApi) ListContainerByImage(c *gin.Context) {
|
||||
// @Success 200 {object} dto.ContainerStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param containersOnly query boolean false "Only count containers"
|
||||
// @Router /containers/status [get]
|
||||
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
||||
data, err := containerService.LoadStatus()
|
||||
data, err := containerService.LoadStatus(c.Request.Context(), c.Query("containersOnly") == "true")
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -415,9 +416,14 @@ func (b *BaseApi) LoadResourceLimit(c *gin.Context) {
|
||||
// @Success 200 {array} dto.ContainerListStats
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Param ids query string false "Comma-separated container IDs; omitted selects all containers"
|
||||
// @Router /containers/list/stats [get]
|
||||
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
||||
data, err := containerService.ContainerListStats()
|
||||
var ids []string
|
||||
if _, supplied := c.Request.URL.Query()["ids"]; supplied {
|
||||
ids = strings.FieldsFunc(c.Query("ids"), func(r rune) bool { return r == ',' })
|
||||
}
|
||||
data, err := containerService.ContainerListStats(c.Request.Context(), ids)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -964,3 +970,12 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
||||
|
||||
containerService.StreamLogs(c, streamLog)
|
||||
}
|
||||
|
||||
func (b *BaseApi) CleanNetworks(c *gin.Context) {
|
||||
result, err := containerService.CleanNetworks()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
@@ -86,17 +86,38 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
||||
|
||||
// @Tags Database Redis
|
||||
// @Summary Install redis-cli
|
||||
// @Success 200
|
||||
// @Accept json
|
||||
// @Param request body dto.RedisCliInstall true "request"
|
||||
// @Success 200 {object} dto.RedisCliStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/redis/install/cli [post]
|
||||
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)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
// @Tags Database Redis
|
||||
// @Summary Load redis-cli installation status
|
||||
// @Success 200 {object} dto.RedisCliStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/redis/cli/status [get]
|
||||
func (b *BaseApi) LoadRedisCliStatus(c *gin.Context) {
|
||||
data, err := redisService.LoadCliStatus()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database Redis
|
||||
|
||||
@@ -169,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [id]","formatEN":"Operate runtime [id]"}
|
||||
// @x-panel-log {"bodyKeys":["ID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ID","isList":false,"db":"runtimes","output_column":"name","output_value":"name"}],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
|
||||
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ type AlertBase struct {
|
||||
}
|
||||
|
||||
type PushAlert struct {
|
||||
Result string `json:"result,omitempty"`
|
||||
TaskName string `json:"taskName"`
|
||||
AlertType string `json:"alertType"`
|
||||
EntryID uint `json:"entryID"`
|
||||
@@ -53,6 +54,7 @@ type AlertDTO struct {
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
TaskName string `json:"taskName,omitempty"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package dto
|
||||
|
||||
type NetworkCleanupReport struct {
|
||||
Deleted []NetworkCleanupItem `json:"deleted"`
|
||||
Skipped []NetworkCleanupItem `json:"skipped"`
|
||||
Failed []NetworkCleanupItem `json:"failed"`
|
||||
}
|
||||
|
||||
type NetworkCleanupItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type NetworkCleanupTask struct {
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
@@ -51,9 +51,10 @@ type CronjobOperate struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
|
||||
CleanLogConfig
|
||||
}
|
||||
@@ -126,7 +127,8 @@ type CronjobInfo struct {
|
||||
Secret string `json:"secret"`
|
||||
Args string `json:"args"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode"`
|
||||
}
|
||||
|
||||
type CronjobImport struct {
|
||||
@@ -169,9 +171,10 @@ type CronjobTrans struct {
|
||||
SourceAccounts []string `json:"sourceAccounts"`
|
||||
DownloadAccount string `json:"downloadAccount"`
|
||||
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
AlertTitle string `json:"alertTitle"`
|
||||
AlertMethod string `json:"alertMethod"`
|
||||
AlertTriggerMode string `json:"alertTriggerMode" validate:"omitempty,oneof=failed success both"`
|
||||
}
|
||||
type TransHelper struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -22,6 +22,17 @@ type DBBaseInfo struct {
|
||||
Port int64 `json:"port"`
|
||||
}
|
||||
|
||||
type RedisCliInstall struct {
|
||||
TaskID string `json:"taskID" validate:"omitempty,uuid"`
|
||||
}
|
||||
|
||||
type RedisCliStatus struct {
|
||||
Installed bool `json:"installed"`
|
||||
TaskID string `json:"taskID"`
|
||||
Status string `json:"status"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
// mysql
|
||||
type MysqlDBSearch struct {
|
||||
PageInfo
|
||||
|
||||
+44
-11
@@ -44,6 +44,8 @@ type MonitorGPUOptions struct {
|
||||
Options []string `json:"options"`
|
||||
}
|
||||
type GPUChartHide struct {
|
||||
DeviceID string `json:"deviceID"`
|
||||
Legacy bool `json:"legacy"`
|
||||
ProductName string `json:"productName"`
|
||||
Type string `json:"type"`
|
||||
Process bool `json:"process"`
|
||||
@@ -55,23 +57,54 @@ type GPUChartHide struct {
|
||||
Speed bool `json:"speed"`
|
||||
}
|
||||
type MonitorGPUSearch struct {
|
||||
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
|
||||
DeviceID string `json:"deviceID"`
|
||||
Legacy bool `json:"legacy"`
|
||||
ProductName string `json:"productName"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
}
|
||||
type MonitorGPUData struct {
|
||||
Date []time.Time `json:"date"`
|
||||
GPUValue []float64 `json:"gpuValue"`
|
||||
TemperatureValue []float64 `json:"temperatureValue"`
|
||||
PowerTotal []float64 `json:"powerTotal"`
|
||||
PowerUsed []float64 `json:"powerUsed"`
|
||||
PowerPercent []float64 `json:"powerPercent"`
|
||||
MemoryTotal []float64 `json:"memoryTotal"`
|
||||
MemoryUsed []float64 `json:"memoryUsed"`
|
||||
MemoryPercent []float64 `json:"memoryPercent"`
|
||||
SpeedValue []int `json:"speedValue"`
|
||||
MemoryActivity []*float64 `json:"memoryActivity"`
|
||||
EncoderUtil []*float64 `json:"encoderUtil"`
|
||||
DecoderUtil []*float64 `json:"decoderUtil"`
|
||||
JPEGUtil []*float64 `json:"jpegUtil"`
|
||||
OFAUtil []*float64 `json:"ofaUtil"`
|
||||
MediaUtil []*float64 `json:"mediaUtil"`
|
||||
ComputeUtil []*float64 `json:"computeUtil"`
|
||||
CopyUtil []*float64 `json:"copyUtil"`
|
||||
HotspotTemperature []*float64 `json:"hotspotTemperature"`
|
||||
FanRPM []*float64 `json:"fanRPM"`
|
||||
AICPUUtil []*float64 `json:"aiCPUUtil"`
|
||||
CtrlCPUUtil []*float64 `json:"ctrlCPUUtil"`
|
||||
DDRUsed []*float64 `json:"ddrUsed"`
|
||||
DDRTotal []*float64 `json:"ddrTotal"`
|
||||
HBMUsed []*float64 `json:"hbmUsed"`
|
||||
HBMTotal []*float64 `json:"hbmTotal"`
|
||||
DDRBandwidth []*float64 `json:"ddrBandwidth"`
|
||||
HBMBandwidth []*float64 `json:"hbmBandwidth"`
|
||||
MemoryBandwidth []*float64 `json:"memoryBandwidth"`
|
||||
MediaFrequency []*float64 `json:"mediaFrequency"`
|
||||
HugepagesUsed []*float64 `json:"hugepagesUsed"`
|
||||
HugepagesTotal []*float64 `json:"hugepagesTotal"`
|
||||
|
||||
ProcessCount []int `json:"processCount"`
|
||||
BucketSeconds int64 `json:"bucketSeconds"`
|
||||
SampleCount int64 `json:"sampleCount"`
|
||||
MemoryTemperatureValue []*float64 `json:"memoryTemperatureValue"`
|
||||
FrequencyValue []*float64 `json:"frequencyValue"`
|
||||
MemoryFrequencyValue []*float64 `json:"memoryFrequencyValue"`
|
||||
Date []time.Time `json:"date"`
|
||||
GPUValue []*float64 `json:"gpuValue"`
|
||||
TemperatureValue []*float64 `json:"temperatureValue"`
|
||||
PowerTotal []*float64 `json:"powerTotal"`
|
||||
PowerUsed []*float64 `json:"powerUsed"`
|
||||
PowerPercent []*float64 `json:"powerPercent"`
|
||||
MemoryTotal []*float64 `json:"memoryTotal"`
|
||||
MemoryUsed []*float64 `json:"memoryUsed"`
|
||||
MemoryPercent []*float64 `json:"memoryPercent"`
|
||||
SpeedValue []*float64 `json:"speedValue"`
|
||||
|
||||
ProcessCount []*float64 `json:"processCount"`
|
||||
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
|
||||
}
|
||||
|
||||
|
||||
@@ -33,14 +33,45 @@ type MonitorNetwork struct {
|
||||
}
|
||||
|
||||
type MonitorGPU struct {
|
||||
MemoryUtil *float64 `json:"memoryUtil"`
|
||||
MemoryActivity *float64 `json:"memoryActivity"`
|
||||
EncoderUtil *float64 `json:"encoderUtil"`
|
||||
DecoderUtil *float64 `json:"decoderUtil"`
|
||||
JPEGUtil *float64 `json:"jpegUtil"`
|
||||
OFAUtil *float64 `json:"ofaUtil"`
|
||||
MediaUtil *float64 `json:"mediaUtil"`
|
||||
ComputeUtil *float64 `json:"computeUtil"`
|
||||
CopyUtil *float64 `json:"copyUtil"`
|
||||
HotspotTemperature *float64 `json:"hotspotTemperature"`
|
||||
FanRPM *float64 `json:"fanRPM"`
|
||||
AICPUUtil *float64 `json:"aiCPUUtil"`
|
||||
CtrlCPUUtil *float64 `json:"ctrlCPUUtil"`
|
||||
DDRUsed *float64 `json:"ddrUsed"`
|
||||
DDRTotal *float64 `json:"ddrTotal"`
|
||||
HBMUsed *float64 `json:"hbmUsed"`
|
||||
HBMTotal *float64 `json:"hbmTotal"`
|
||||
DDRBandwidth *float64 `json:"ddrBandwidth"`
|
||||
HBMBandwidth *float64 `json:"hbmBandwidth"`
|
||||
MemoryBandwidth *float64 `json:"memoryBandwidth"`
|
||||
MediaFrequency *float64 `json:"mediaFrequency"`
|
||||
HugepagesUsed *float64 `json:"hugepagesUsed"`
|
||||
HugepagesTotal *float64 `json:"hugepagesTotal"`
|
||||
|
||||
MemoryTemperature *float64 `json:"memoryTemperature"`
|
||||
DeviceID string `json:"deviceID"`
|
||||
DeviceType string `json:"deviceType"`
|
||||
ProcessStatus string `json:"processStatus"`
|
||||
Frequency *float64 `json:"frequency"`
|
||||
MemoryFrequency *float64 `json:"memoryFrequency"`
|
||||
IntervalSeconds int `json:"intervalSeconds"`
|
||||
BaseModel
|
||||
ProductName string `json:"productName"`
|
||||
GPUUtil float64 `json:"gpuUtil"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
PowerDraw float64 `json:"powerDraw"`
|
||||
MaxPowerLimit float64 `json:"maxPowerLimit"`
|
||||
MemUsed float64 `json:"memUsed"`
|
||||
MemTotal float64 `json:"memTotal"`
|
||||
FanSpeed int `json:"fanSpeed"`
|
||||
Processes string `json:"processes"`
|
||||
ProductName string `json:"productName"`
|
||||
GPUUtil *float64 `json:"gpuUtil"`
|
||||
Temperature *float64 `json:"temperature"`
|
||||
PowerDraw *float64 `json:"powerDraw"`
|
||||
MaxPowerLimit *float64 `json:"maxPowerLimit"`
|
||||
MemUsed *float64 `json:"memUsed"`
|
||||
MemTotal *float64 `json:"memTotal"`
|
||||
FanSpeed *float64 `json:"fanSpeed"`
|
||||
Processes string `json:"processes"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -10,9 +12,20 @@ import (
|
||||
|
||||
type MonitorRepo struct{}
|
||||
|
||||
type GPUHistoryPoint struct {
|
||||
model.MonitorGPU
|
||||
Bucket int64
|
||||
PowerPercent *float64
|
||||
MemoryPercent *float64
|
||||
ProcessCount *float64
|
||||
}
|
||||
|
||||
type IMonitorRepo interface {
|
||||
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
|
||||
GetGPU(opts ...DBOption) ([]model.MonitorGPU, error)
|
||||
CountGPU(opts ...DBOption) (int64, error)
|
||||
GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error)
|
||||
GetGPUDevices() ([]model.MonitorGPU, error)
|
||||
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
|
||||
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
|
||||
|
||||
@@ -26,6 +39,7 @@ type IMonitorRepo interface {
|
||||
DelMonitorNet(timeForDelete time.Time) error
|
||||
|
||||
WithByProductName(name string) DBOption
|
||||
WithByGPUDevice(deviceID, name string, legacy bool) DBOption
|
||||
}
|
||||
|
||||
func NewIMonitorRepo() IMonitorRepo {
|
||||
@@ -102,3 +116,68 @@ func (s *MonitorRepo) WithByProductName(name string) DBOption {
|
||||
return g.Where("product_name = ?", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetGPUDevices() ([]model.MonitorGPU, error) {
|
||||
var data []model.MonitorGPU
|
||||
err := global.GPUMonitorDB.Model(&model.MonitorGPU{}).Select("device_id, product_name, device_type").Group("device_id, product_name, device_type").Order("product_name, device_id").Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) WithByGPUDevice(deviceID, name string, legacy bool) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
if deviceID != "" {
|
||||
return db.Where("device_id = ?", deviceID)
|
||||
}
|
||||
db = db.Where("product_name = ?", name)
|
||||
if legacy {
|
||||
db = db.Where("device_id IS NULL OR device_id = ''")
|
||||
}
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) CountGPU(opts ...DBOption) (int64, error) {
|
||||
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
var count int64
|
||||
err := db.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (u *MonitorRepo) GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error) {
|
||||
db := global.GPUMonitorDB.Model(&model.MonitorGPU{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
expressions := []string{
|
||||
"CASE WHEN max_power_limit > 0 THEN 100.0 * power_draw / max_power_limit END",
|
||||
"CASE WHEN mem_total > 0 AND mem_used IS NOT NULL THEN 100.0 * mem_used / mem_total ELSE memory_util END",
|
||||
"CASE WHEN (process_status = 'ok' OR process_status IS NULL OR process_status = '') AND json_valid(processes) THEN CASE WHEN json_type(processes) = 'array' THEN json_array_length(processes) END END",
|
||||
}
|
||||
aliases := []string{"power_percent", "memory_percent", "process_count"}
|
||||
columns := []string{"*"}
|
||||
if bucketSeconds > 0 {
|
||||
operation := "AVG"
|
||||
if aggregation == "max" {
|
||||
operation = "MAX"
|
||||
}
|
||||
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), bucketSeconds)}
|
||||
for _, column := range []string{"memory_activity", "encoder_util", "decoder_util", "jpeg_util", "ofa_util", "media_util", "compute_util", "copy_util", "hotspot_temperature", "fan_rpm", "ai_cpu_util", "ctrl_cpu_util", "ddr_used", "ddr_total", "hbm_used", "hbm_total", "ddr_bandwidth", "hbm_bandwidth", "memory_bandwidth", "media_frequency", "hugepages_used", "hugepages_total", "gpu_util", "temperature", "memory_temperature", "power_draw", "max_power_limit", "mem_used", "mem_total", "frequency", "memory_frequency", "fan_speed"} {
|
||||
columns = append(columns, operation+"("+column+") AS "+column)
|
||||
}
|
||||
for i := range expressions {
|
||||
expressions[i] = operation + "(" + expressions[i] + ")"
|
||||
}
|
||||
db = db.Group("bucket").Order("bucket ASC")
|
||||
} else {
|
||||
db = db.Order("created_at ASC, id ASC")
|
||||
}
|
||||
for i, expression := range expressions {
|
||||
columns = append(columns, expression+" AS "+aliases[i])
|
||||
}
|
||||
var data []GPUHistoryPoint
|
||||
err := db.Select(strings.Join(columns, ", ")).Scan(&data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
alertconfig "github.com/1Panel-dev/1Panel/agent/utils/alert_config"
|
||||
alertwebhook "github.com/1Panel-dev/1Panel/agent/utils/alert_webhook"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
@@ -109,7 +110,38 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
cronjobProjects := make(map[string]uint)
|
||||
var cronjobIDs []uint
|
||||
for _, item := range alerts {
|
||||
if alertUtil.GetCronJobType(item.Type) != "cronJob" {
|
||||
continue
|
||||
}
|
||||
if _, exists := cronjobProjects[item.Project]; exists {
|
||||
continue
|
||||
}
|
||||
id, parseErr := strconv.ParseUint(item.Project, 10, strconv.IntSize)
|
||||
if parseErr != nil || id == 0 {
|
||||
continue
|
||||
}
|
||||
cronjobProjects[item.Project] = uint(id)
|
||||
cronjobIDs = append(cronjobIDs, uint(id))
|
||||
}
|
||||
cronjobsByID := make(map[uint]model.Cronjob)
|
||||
if len(cronjobIDs) > 0 {
|
||||
cronjobs, err := cronjobRepo.List(repo.WithByIDs(cronjobIDs))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, cronjob := range cronjobs {
|
||||
cronjobsByID[cronjob.ID] = cronjob
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range alerts {
|
||||
var taskName string
|
||||
if cronjob, exists := cronjobsByID[cronjobProjects[item.Project]]; exists && cronjob.Type == item.Type {
|
||||
taskName = cronjob.Name
|
||||
}
|
||||
|
||||
result = append(result, dto.AlertDTO{
|
||||
ID: item.ID,
|
||||
@@ -119,6 +151,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
TaskName: taskName,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
@@ -190,6 +223,16 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
advanced, err := prepareCronJobAlertParams(create.Type, "", create.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
create.AdvancedParams = advanced
|
||||
if create.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(create.Type, advanced, create.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
alertInfo.Status = constant.AlertEnable
|
||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
@@ -207,11 +250,24 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||
if alertUtil.GetCronJobType(req.Type) == "cronJob" {
|
||||
previous, err := alertRepo.Get(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.AdvancedParams, err = prepareCronJobAlertParams(req.Type, previous.AdvancedParams, req.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
methodTypes, err := a.validateAlertMethodReferences(req.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Status != constant.AlertDisable {
|
||||
if err := a.validateCronJobAlertChannels(req.Type, req.AdvancedParams, req.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -278,6 +334,9 @@ func (a AlertService) UpdateStatus(id uint, status string) error {
|
||||
return err
|
||||
}
|
||||
if status == constant.AlertEnable {
|
||||
if err := a.validateCronJobAlertChannels(alertInfo.Type, alertInfo.AdvancedParams, alertInfo.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.validateAlertMethodEntitlement(methodTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1021,6 +1080,23 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
alertRepo.WithByType(updateAlert.Type),
|
||||
alertRepo.WithByProject(updateAlert.Project),
|
||||
)
|
||||
advanced, err := prepareCronJobAlertParams(updateAlert.Type, alertInfo.AdvancedParams, updateAlert.AdvancedParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAlert.AdvancedParams = advanced
|
||||
if alertUtil.GetCronJobType(updateAlert.Type) == "cronJob" {
|
||||
upMap["advanced_params"] = advanced
|
||||
}
|
||||
if newStatus == constant.AlertEnable {
|
||||
method := updateAlert.Method
|
||||
if method == "" {
|
||||
method = alertInfo.Method
|
||||
}
|
||||
if err := a.validateCronJobAlertChannels(updateAlert.Type, advanced, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if alertInfo.ID > 0 {
|
||||
shouldUpdate := false
|
||||
@@ -1034,6 +1110,9 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
|
||||
shouldUpdate = true
|
||||
}
|
||||
if val, ok := upMap["advanced_params"]; ok && val != alertInfo.AdvancedParams {
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
if shouldUpdate {
|
||||
if err := alertRepo.Update(
|
||||
@@ -1055,3 +1134,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareCronJobAlertParams(alertType, previous, incoming string) (string, error) {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return incoming, nil
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams(previous, incoming)
|
||||
}
|
||||
|
||||
func (a AlertService) validateCronJobAlertChannels(alertType, advanced, method string) error {
|
||||
if alertUtil.GetCronJobType(alertType) != "cronJob" {
|
||||
return nil
|
||||
}
|
||||
mode, err := alertUtil.CronJobAlertTriggerMode(advanced)
|
||||
if err != nil || mode != alertUtil.CronJobAlertSuccess {
|
||||
return err
|
||||
}
|
||||
_, err = a.validateAlertMethodReferences(method)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -530,11 +530,15 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to load ssh login logs: %v", err)
|
||||
}
|
||||
interfaceAddrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Failed to load local IP addresses for ssh login alert: %v", err)
|
||||
}
|
||||
count, records := summarizeSSHLoginHistories(
|
||||
histories,
|
||||
now,
|
||||
failedWindow,
|
||||
strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n"),
|
||||
sshSuccessLoginWhitelist(alert.AdvancedParams, interfaceAddrs),
|
||||
)
|
||||
isAlert := count >= int(alert.Count)
|
||||
if isAlert {
|
||||
@@ -570,6 +574,19 @@ func loadSSHLogin(alert dto.AlertDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func sshSuccessLoginWhitelist(configured string, interfaceAddrs []net.Addr) []string {
|
||||
whitelist := strings.Split(strings.TrimSpace(configured), "\n")
|
||||
whitelist = append(whitelist, "127.0.0.0/8", "::1")
|
||||
for _, addr := range interfaceAddrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok || ipNet.IP == nil || ipNet.IP.IsUnspecified() {
|
||||
continue
|
||||
}
|
||||
whitelist = append(whitelist, ipNet.IP.String())
|
||||
}
|
||||
return whitelist
|
||||
}
|
||||
|
||||
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
||||
filtered := make([]model.LoginLog, 0, len(records))
|
||||
for _, record := range records {
|
||||
|
||||
@@ -58,10 +58,10 @@ type ContainerService struct{}
|
||||
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
||||
|
||||
type IContainerService interface {
|
||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
||||
Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error)
|
||||
List() []dto.ContainerOptions
|
||||
ListByImage(imageName string) []dto.ContainerOptions
|
||||
LoadStatus() (dto.ContainerStatus, error)
|
||||
LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error)
|
||||
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListNetwork() ([]dto.Options, error)
|
||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
@@ -80,7 +80,7 @@ type IContainerService interface {
|
||||
ContainerUpdate(req dto.ContainerOperate) error
|
||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
||||
ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error)
|
||||
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||
ContainerRename(req dto.ContainerRename) error
|
||||
@@ -92,6 +92,7 @@ type IContainerService interface {
|
||||
|
||||
Inspect(req dto.InspectReq) (string, error)
|
||||
DeleteNetwork(req dto.BatchDelete) error
|
||||
CleanNetworks() (*dto.NetworkCleanupTask, error)
|
||||
CreateNetwork(req dto.NetworkCreate) error
|
||||
DeleteVolume(req dto.BatchDelete) error
|
||||
CreateVolume(req dto.VolumeCreate) error
|
||||
@@ -112,7 +113,9 @@ func NewIContainerService() IContainerService {
|
||||
return &ContainerService{}
|
||||
}
|
||||
|
||||
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
||||
func (u *ContainerService) Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
@@ -123,7 +126,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", req.Filters)
|
||||
}
|
||||
containers, err := client.ContainerList(context.Background(), options)
|
||||
containers, err := client.ContainerList(ctx, options)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
@@ -206,27 +209,32 @@ func (u *ContainerService) ListByImage(imageName string) []dto.ContainerOptions
|
||||
return options
|
||||
}
|
||||
|
||||
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
||||
func (u *ContainerService) LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error) {
|
||||
var data dto.ContainerStatus
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
defer client.Close()
|
||||
c := context.Background()
|
||||
|
||||
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
||||
data.ImageCount = len(images)
|
||||
repo, _ := imageRepoRepo.List()
|
||||
data.RepoCount = len(repo)
|
||||
templates, _ := composeRepo.List()
|
||||
data.ComposeTemplateCount = len(templates)
|
||||
networks, _ := client.NetworkList(c, network.ListOptions{})
|
||||
data.NetworkCount = len(networks)
|
||||
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
||||
data.VolumeCount = len(volumes.Volumes)
|
||||
data.ComposeCount = loadComposeCount(client)
|
||||
containers, _ := client.ContainerList(c, container.ListOptions{All: true})
|
||||
c, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
if !containersOnly {
|
||||
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
||||
data.ImageCount = len(images)
|
||||
repo, _ := imageRepoRepo.List()
|
||||
data.RepoCount = len(repo)
|
||||
templates, _ := composeRepo.List()
|
||||
data.ComposeTemplateCount = len(templates)
|
||||
networks, _ := client.NetworkList(c, network.ListOptions{})
|
||||
data.NetworkCount = len(networks)
|
||||
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
||||
data.VolumeCount = len(volumes.Volumes)
|
||||
data.ComposeCount = loadComposeCount(c, client)
|
||||
}
|
||||
containers, err := client.ContainerList(c, container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data.ContainerCount = len(containers)
|
||||
for _, item := range containers {
|
||||
switch item.State {
|
||||
@@ -292,27 +300,67 @@ func (u *ContainerService) ContainerItemStats(ctx context.Context, req dto.Opera
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
|
||||
func (u *ContainerService) ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
list, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
options := container.ListOptions{All: true}
|
||||
if ids != nil {
|
||||
if len(ids) == 0 {
|
||||
return []dto.ContainerListStats{}, nil
|
||||
}
|
||||
options.Filters = filters.NewArgs()
|
||||
for _, id := range ids {
|
||||
options.Filters.Add("id", id)
|
||||
}
|
||||
}
|
||||
list, err := client.ContainerList(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return collectContainerStats(ctx, list, func(ctx context.Context, id string) dto.ContainerListStats {
|
||||
return loadCpuAndMem(ctx, client, id)
|
||||
}), nil
|
||||
}
|
||||
|
||||
// A fixed worker pool bounds Docker stats requests, including for legacy callers
|
||||
// that request all containers. Stopped containers do not need a stats sample.
|
||||
func collectContainerStats(ctx context.Context, list []container.Summary, load func(context.Context, string) dto.ContainerListStats) []dto.ContainerListStats {
|
||||
datas := make([]dto.ContainerListStats, len(list))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(list))
|
||||
for i := 0; i < len(list); i++ {
|
||||
go func(index int, item container.Summary) {
|
||||
datas[index] = loadCpuAndMem(client, item.ID)
|
||||
wg.Done()
|
||||
}(i, list[i])
|
||||
for i, item := range list {
|
||||
datas[i].ContainerID = item.ID
|
||||
}
|
||||
jobs := make(chan int)
|
||||
var wg sync.WaitGroup
|
||||
for worker := 0; worker < min(8, len(list)); worker++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for index := range jobs {
|
||||
if ctx.Err() != nil || list[index].State != "running" {
|
||||
continue
|
||||
}
|
||||
sampleCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
datas[index] = load(sampleCtx, list[index].ID)
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
}
|
||||
dispatch:
|
||||
for index := range list {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break dispatch
|
||||
case jobs <- index:
|
||||
}
|
||||
}
|
||||
close(jobs)
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
return datas
|
||||
}
|
||||
|
||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
@@ -484,6 +532,10 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
|
||||
return u.containerCreate(req, inThread, "")
|
||||
}
|
||||
|
||||
func (u *ContainerService) containerCreate(req dto.ContainerOperate, inThread bool, taskName string) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -497,7 +549,10 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
||||
return buserr.New("ErrContainerName")
|
||||
}
|
||||
|
||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||
if taskName == "" {
|
||||
taskName = task.GetTaskName(req.Name, task.TaskCreate, task.TaskScopeContainer)
|
||||
}
|
||||
taskItem, err := task.NewTask(taskName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||
if err != nil {
|
||||
unlock()
|
||||
_ = client.Close()
|
||||
@@ -558,6 +613,11 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
||||
}, nil)
|
||||
|
||||
if inThread {
|
||||
if err := taskItem.Prepare(); err != nil {
|
||||
unlock()
|
||||
_ = client.Close()
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer unlock()
|
||||
defer client.Close()
|
||||
@@ -1715,11 +1775,11 @@ func selectImageRepo(imageName string, repos []model.ImageRepo) *model.ImageRepo
|
||||
return selected
|
||||
}
|
||||
|
||||
func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
|
||||
func loadCpuAndMem(ctx context.Context, client *client.Client, containerItem string) dto.ContainerListStats {
|
||||
data := dto.ContainerListStats{
|
||||
ContainerID: containerItem,
|
||||
}
|
||||
res, err := client.ContainerStats(context.Background(), containerItem, false)
|
||||
res, err := client.ContainerStats(ctx, containerItem, false)
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
@@ -1942,11 +2002,11 @@ func transPortToStr(ports []container.Port) []string {
|
||||
return docker.SimplifyPorts(ports)
|
||||
}
|
||||
|
||||
func loadComposeCount(client *client.Client) int {
|
||||
func loadComposeCount(ctx context.Context, client *client.Client) int {
|
||||
options := container.ListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", composeProjectLabel)
|
||||
list, err := client.ContainerList(context.Background(), options)
|
||||
list, err := client.ContainerList(ctx, options)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
)
|
||||
|
||||
var networkCleanupMu sync.Mutex
|
||||
|
||||
func (u *ContainerService) CleanNetworks() (*dto.NetworkCleanupTask, error) {
|
||||
taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey("Network"), task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
||||
networkCleanupMu.Lock()
|
||||
defer networkCleanupMu.Unlock()
|
||||
if err := t.TaskCtx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
cli, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
return executeNetworkCleanup(t, cli)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
global.LOG.Errorf("network cleanup task %s failed: %v", taskItem.TaskID, err)
|
||||
}
|
||||
}()
|
||||
return &dto.NetworkCleanupTask{TaskID: taskItem.TaskID}, nil
|
||||
}
|
||||
|
||||
func executeNetworkCleanup(t *task.Task, cli docker.NetworkCleanupClient) error {
|
||||
t.Log(i18n.GetMsgByKey("PruneStart"))
|
||||
report, err := docker.CleanUnusedNetworks(t.TaskCtx, cli, func(status string, item dto.NetworkCleanupItem) {
|
||||
key := "NetworkCleanupDeleted"
|
||||
if status == "skipped" || status == "failed" {
|
||||
key = map[string]string{
|
||||
"protected": "NetworkCleanupProtected",
|
||||
"container_connected": "NetworkCleanupConnected",
|
||||
"network_in_use": "NetworkCleanupConnected",
|
||||
"unsupported_network": "NetworkCleanupUnsupported",
|
||||
"already_removed": "NetworkCleanupGone",
|
||||
"inspect_failed": "NetworkCleanupInspectFailed",
|
||||
"remove_failed": "NetworkCleanupRemoveFailed",
|
||||
}[item.Reason]
|
||||
}
|
||||
t.Log(i18n.GetMsgWithMap(key, map[string]interface{}{"name": item.Name, "id": item.ID}))
|
||||
})
|
||||
if report != nil {
|
||||
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
|
||||
"deleted": len(report.Deleted), "skipped": len(report.Skipped), "failed": len(report.Failed),
|
||||
}))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(report.Failed) > 0 {
|
||||
return fmt.Errorf("%s", i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type networkTaskClient struct{ fail bool }
|
||||
|
||||
func (f networkTaskClient) NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error) {
|
||||
return []network.Inspect{{ID: "reserved", Name: "1panel-network", Scope: "local"}, {ID: "connected", Name: "busy", Scope: "local"}, {ID: "unused", Name: "free", Scope: "local"}, {ID: "race", Name: "race", Scope: "local"}}, nil
|
||||
}
|
||||
func (f networkTaskClient) ContainerList(context.Context, container.ListOptions) ([]container.Summary, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f networkTaskClient) NetworkInspect(_ context.Context, id string, _ network.InspectOptions) (network.Inspect, error) {
|
||||
n := network.Inspect{}
|
||||
if id == "connected" {
|
||||
n.Containers = map[string]network.EndpointResource{"container-id": {}}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
func (f networkTaskClient) NetworkRemove(_ context.Context, id string) error {
|
||||
if id == "race" {
|
||||
return errdefs.Conflict(errors.New("has active endpoints"))
|
||||
}
|
||||
if id != "unused" {
|
||||
return errors.New("unexpected removal")
|
||||
}
|
||||
if f.fail {
|
||||
return errors.New("remove failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNetworkCleanupTaskPersistsLogsAndStatus(t *testing.T) {
|
||||
oldDB, oldTaskDB, oldDir, oldI18n := global.DB, global.TaskDB, global.Dir, global.I18n
|
||||
t.Cleanup(func() { global.DB = oldDB; global.TaskDB = oldTaskDB; global.Dir = oldDir; global.I18n = oldI18n })
|
||||
dir := t.TempDir()
|
||||
db, err := gorm.Open(sqlite.Open(filepath.Join(dir, "tasks.db")), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
if err := db.AutoMigrate(&model.Task{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
global.DB = nil
|
||||
global.TaskDB = db
|
||||
global.Dir.TaskDir = dir
|
||||
i18n.Init()
|
||||
for _, fail := range []bool{false, true} {
|
||||
name := "success"
|
||||
wantStatus := constant.StatusSuccess
|
||||
if fail {
|
||||
name = "partial failure"
|
||||
wantStatus = constant.StatusFailed
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
item, err := task.NewTaskWithOps("Network", task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
item.AddSubTask("Clean", func(t *task.Task) error { return executeNetworkCleanup(t, networkTaskClient{fail: fail}) }, nil)
|
||||
err = item.Execute()
|
||||
if (err != nil) != fail {
|
||||
t.Fatalf("unexpected execution error: %v", err)
|
||||
}
|
||||
var saved model.Task
|
||||
if err := db.First(&saved, "id = ?", item.TaskID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if saved.Status != wantStatus {
|
||||
t.Fatalf("status %s, want %s", saved.Status, wantStatus)
|
||||
}
|
||||
content, err := os.ReadFile(saved.LogFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{"[busy] (connected): containers connected", "[race] (race): containers connected", "[1panel-network] (reserved): reserved network", "Network cleanup finished:", "[TASK-END]"} {
|
||||
if !strings.Contains(string(content), want) {
|
||||
t.Fatalf("missing %q in log: %s", want, content)
|
||||
}
|
||||
}
|
||||
want := "Deleted network [free]"
|
||||
if fail {
|
||||
want = "Failed to remove network [free]"
|
||||
}
|
||||
if !strings.Contains(string(content), want) {
|
||||
t.Fatalf("missing %q", want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -75,6 +76,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
@@ -98,9 +100,11 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
}
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))))
|
||||
item.AlertMethod = alertInfo.Method
|
||||
if alertInfo.SendCount != 0 {
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
@@ -195,11 +199,12 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
}
|
||||
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
|
||||
if alertInfo.SendCount != 0 {
|
||||
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))))
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
item.AlertTriggerMode, _ = alertUtil.CronJobAlertTriggerMode(alertInfo.AdvancedParams)
|
||||
if alertInfo.Status == constant.AlertEnable {
|
||||
item.AlertCount = alertInfo.SendCount
|
||||
item.AlertTitle = alertInfo.Title
|
||||
item.AlertMethod = alertInfo.Method
|
||||
} else {
|
||||
item.AlertCount = 0
|
||||
}
|
||||
@@ -213,6 +218,17 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
for _, item := range req {
|
||||
advanced, err := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(item.Type, advanced, item.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range req {
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||
if cronjobItem.ID != 0 {
|
||||
@@ -395,17 +411,27 @@ func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
} else {
|
||||
cronjob.Status = constant.StatusDisable
|
||||
}
|
||||
_ = cronjobRepo.Create(&cronjob)
|
||||
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
if err := cronjobRepo.Create(&cronjob); err != nil {
|
||||
return err
|
||||
}
|
||||
if item.AlertTitle != "" && item.AlertMethod != "" {
|
||||
advanced, _ := cronJobAlertAdvancedParams(item.AlertTriggerMode)
|
||||
status := constant.AlertEnable
|
||||
if item.AlertCount == 0 {
|
||||
status = constant.AlertDisable
|
||||
}
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: item.AlertTitle,
|
||||
SendCount: item.AlertCount,
|
||||
Method: item.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: status,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
if err := NewIAlertService().CreateAlert(createAlert, operator); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -562,6 +588,15 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(req.Type, advanced, req.AlertMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||
if cronjob.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -603,12 +638,13 @@ func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
}
|
||||
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
|
||||
createAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
@@ -682,6 +718,10 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
}
|
||||
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
||||
advanced, err := cronJobAlertAdvancedParams(req.AlertTriggerMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cronjob model.Cronjob
|
||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
@@ -697,6 +737,20 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
if err != nil {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if req.AlertCount != 0 {
|
||||
previous, _ := alertRepo.Get(alertRepo.WithByType(cronModel.Type), alertRepo.WithByProject(strconv.Itoa(int(id))))
|
||||
merged, err := prepareCronJobAlertParams(cronModel.Type, previous.AdvancedParams, advanced)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
method := req.AlertMethod
|
||||
if method == "" {
|
||||
method = previous.Method
|
||||
}
|
||||
if err := (AlertService{}).validateCronJobAlertChannels(cronModel.Type, merged, method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
upMap := make(map[string]interface{})
|
||||
cronjob.EntryIDs = cronModel.EntryIDs
|
||||
cronjob.Type = cronModel.Type
|
||||
@@ -753,11 +807,12 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return err
|
||||
}
|
||||
updateAlert := dto.AlertCreate{
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
Title: req.AlertTitle,
|
||||
SendCount: req.AlertCount,
|
||||
Method: req.AlertMethod,
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
AdvancedParams: advanced,
|
||||
}
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
@@ -766,6 +821,17 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string
|
||||
return nil
|
||||
}
|
||||
|
||||
func cronJobAlertAdvancedParams(mode string) (string, error) {
|
||||
if mode == "" {
|
||||
return "", nil
|
||||
}
|
||||
data, err := json.Marshal(map[string]string{"alertTriggerMode": mode})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return alertUtil.MergeCronJobAlertParams("", string(data))
|
||||
}
|
||||
|
||||
func (u *CronjobService) UpdateStatus(id uint, status string) error {
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
|
||||
if cronjob.ID == 0 {
|
||||
|
||||
@@ -412,6 +412,7 @@ func addSkipTask(source string, taskItem *task.Task) {
|
||||
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
||||
return nil
|
||||
}, nil)
|
||||
taskItem.SubTasks[len(taskItem.SubTasks)-1].StepAlias = cronJobSkippedStep
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
|
||||
@@ -56,10 +58,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
_ = taskRepo.Save(context.Background(), taskItem.Task)
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}()
|
||||
return
|
||||
}
|
||||
@@ -70,19 +73,20 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := taskItem.Execute(); err != nil {
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(taskItem.ID) == 0 {
|
||||
storedTask, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(storedTask.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
handleCronJobAlert(cronjob)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, err))
|
||||
} else {
|
||||
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
|
||||
handleCronJobAlert(cronjob, cronJobAlertResult(taskItem, nil))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -482,8 +486,33 @@ func hasBackup(cronjobType string) bool {
|
||||
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob) {
|
||||
const cronJobSkippedStep = "cronjob-skipped"
|
||||
|
||||
func cronJobAlertResult(taskItem *task.Task, err error) string {
|
||||
if errors.Is(err, context.Canceled) || taskItem.Task.Status == constant.StatusCanceled ||
|
||||
(taskItem.TaskCtx != nil && taskItem.TaskCtx.Err() != nil) {
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
return alertUtil.CronJobAlertFailed
|
||||
}
|
||||
if taskItem.Task.Status != constant.StatusSuccess {
|
||||
return ""
|
||||
}
|
||||
for _, subTask := range taskItem.SubTasks {
|
||||
if subTask.StepAlias != cronJobSkippedStep {
|
||||
return alertUtil.CronJobAlertSuccess
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleCronJobAlert(cronjob *model.Cronjob, result string) {
|
||||
if result == "" {
|
||||
return
|
||||
}
|
||||
pushAlert := dto.PushAlert{
|
||||
Result: result,
|
||||
TaskName: cronjob.Name,
|
||||
AlertType: cronjob.Type,
|
||||
EntryID: cronjob.ID,
|
||||
|
||||
@@ -588,6 +588,9 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||
xpuData []dto.XPUInfo
|
||||
)
|
||||
for _, device := range snapshot.Devices {
|
||||
if device.ParentID != "" {
|
||||
continue
|
||||
}
|
||||
switch device.Kind {
|
||||
case accelerator.KindGPU:
|
||||
if device.GPU == nil {
|
||||
@@ -597,7 +600,11 @@ func loadAcceleratorInfo() ([]dto.GPUInfo, []dto.NPUInfo, []dto.XPUInfo) {
|
||||
if err := copier.Copy(&dataItem, device.GPU); err != nil {
|
||||
continue
|
||||
}
|
||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
|
||||
dataItem.MaxPowerLimit = device.GPU.PowerLimit
|
||||
dataItem.PowerUsage = dataItem.PowerDraw
|
||||
if dataItem.MaxPowerLimit != "" {
|
||||
dataItem.PowerUsage += " / " + dataItem.MaxPowerLimit
|
||||
}
|
||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
||||
gpuData = append(gpuData, dataItem)
|
||||
case accelerator.KindNPU:
|
||||
|
||||
@@ -8,6 +8,12 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"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/global"
|
||||
@@ -23,6 +29,11 @@ import (
|
||||
|
||||
type RedisService struct{}
|
||||
|
||||
const redisCliTaskName = "RedisCliEnable"
|
||||
|
||||
// The CLI container is shared by all remote Redis databases on this node.
|
||||
var redisCliInstallMutex sync.Mutex
|
||||
|
||||
type IRedisService interface {
|
||||
UpdateConf(req dto.RedisConfUpdate) error
|
||||
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
||||
@@ -33,7 +44,8 @@ type IRedisService interface {
|
||||
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
||||
|
||||
CheckHasCli() bool
|
||||
InstallCli() error
|
||||
InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error)
|
||||
LoadCliStatus() (*dto.RedisCliStatus, error)
|
||||
}
|
||||
|
||||
func NewIRedisService() IRedisService {
|
||||
@@ -71,20 +83,62 @@ func (u *RedisService) CheckHasCli() bool {
|
||||
return false
|
||||
}
|
||||
for _, item := range containerLists {
|
||||
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
|
||||
if len(item.Names) > 0 && strings.TrimPrefix(item.Names[0], "/") == "1Panel-redis-cli-tools" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *RedisService) InstallCli() error {
|
||||
func (u *RedisService) LoadCliStatus() (*dto.RedisCliStatus, error) {
|
||||
result := &dto.RedisCliStatus{}
|
||||
latest, err := taskRepo.GetFirst(repo.WithByName(redisCliTaskName), repo.WithByType(task.TaskScopeContainer), repo.WithOrderDesc("created_at"))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
result.Installed = u.CheckHasCli()
|
||||
return result, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.TaskID = latest.ID
|
||||
result.Status = latest.Status
|
||||
result.ErrorMsg = latest.ErrorMsg
|
||||
result.Installed = u.CheckHasCli()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error) {
|
||||
if !redisCliInstallMutex.TryLock() {
|
||||
return nil, buserr.New("TaskIsExecuting")
|
||||
}
|
||||
defer redisCliInstallMutex.Unlock()
|
||||
status, err := u.LoadCliStatus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status.Status == constant.StatusExecuting || status.Installed {
|
||||
return status, nil
|
||||
}
|
||||
if req.TaskID == "" {
|
||||
req.TaskID = uuid.NewString()
|
||||
}
|
||||
// Never reuse an existing task ID: doing so would truncate its log.
|
||||
if _, err := taskRepo.GetFirst(taskRepo.WithByID(req.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, buserr.New("TaskIsExecuting")
|
||||
}
|
||||
item := dto.ContainerOperate{
|
||||
TaskID: req.TaskID,
|
||||
Name: "1Panel-redis-cli-tools",
|
||||
Image: "redis:7.4.4",
|
||||
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
||||
}
|
||||
return NewIContainerService().ContainerCreate(item, false)
|
||||
if err := (&ContainerService{}).containerCreate(item, true, redisCliTaskName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.RedisCliStatus{TaskID: req.TaskID, Status: constant.StatusExecuting}, nil
|
||||
}
|
||||
|
||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -325,18 +324,16 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
||||
}
|
||||
|
||||
go func() {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
taskItem.Log("Failed to create Docker client: " + err.Error())
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
for _, itemPath := range req.Paths {
|
||||
currentPath := itemPath
|
||||
itemName := path.Base(currentPath)
|
||||
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
||||
taskItem.Logf("----------------- %s -----------------", itemName)
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
file, err := os.Open(currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -347,14 +344,9 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(string(content), "Error") {
|
||||
return errors.New(string(content))
|
||||
}
|
||||
return nil
|
||||
return consumeImageLoadResponse(res.Body, func(message string) {
|
||||
taskItem.Log(message)
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
_ = taskItem.Execute()
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Docker may report load failures in a successful HTTP response's JSON stream.
|
||||
func consumeImageLoadResponse(reader io.Reader, log func(string)) error {
|
||||
decoder := json.NewDecoder(reader)
|
||||
for {
|
||||
var message struct {
|
||||
Stream string `json:"stream"`
|
||||
Error string `json:"error"`
|
||||
ErrorDetail struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"errorDetail"`
|
||||
}
|
||||
if err := decoder.Decode(&message); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if message.Error != "" {
|
||||
return errors.New(message.Error)
|
||||
}
|
||||
if message.ErrorDetail.Message != "" {
|
||||
return errors.New(message.ErrorDetail.Message)
|
||||
}
|
||||
if text := strings.TrimSpace(message.Stream); text != "" {
|
||||
log(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
+216
-104
@@ -128,115 +128,135 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa
|
||||
|
||||
func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
|
||||
var data dto.MonitorGPUOptions
|
||||
exist, client := accelerator.New()
|
||||
if !exist {
|
||||
return data
|
||||
}
|
||||
snapshot, err := client.Collect(context.Background())
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Load accelerator info failed, err: %v", err)
|
||||
return data
|
||||
}
|
||||
if warning := snapshot.Warning(); warning != nil {
|
||||
global.LOG.Warnf("Load accelerator info partially failed, err: %v", warning)
|
||||
}
|
||||
return loadGPUOptions(snapshot)
|
||||
}
|
||||
|
||||
func loadGPUOptions(snapshot *accelerator.Snapshot) dto.MonitorGPUOptions {
|
||||
var data dto.MonitorGPUOptions
|
||||
hasGPUOrNPU := false
|
||||
hasXPU := false
|
||||
for _, item := range snapshot.Devices {
|
||||
if item.Kind == accelerator.KindXPU {
|
||||
hasXPU = true
|
||||
seen := make(map[string]bool)
|
||||
if exist, client := accelerator.New(); exist {
|
||||
snapshot, err := client.Collect(context.Background())
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Load accelerator options failed: %v", err)
|
||||
} else {
|
||||
hasGPUOrNPU = true
|
||||
data = loadGPUOptions(snapshot)
|
||||
for _, item := range data.ChartHide {
|
||||
seen[item.DeviceID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case hasGPUOrNPU && hasXPU:
|
||||
data.GPUType = "mixed"
|
||||
case hasXPU:
|
||||
data.GPUType = "xpu"
|
||||
case hasGPUOrNPU:
|
||||
data.GPUType = "gpu"
|
||||
devices, err := monitorRepo.GetGPUDevices()
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Load accelerator history options failed: %v", err)
|
||||
return data
|
||||
}
|
||||
|
||||
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
|
||||
for _, device := range devices {
|
||||
key := device.DeviceID
|
||||
if key == "" {
|
||||
key = "legacy:" + device.ProductName
|
||||
}
|
||||
if snapshot.Devices[i].Vendor != snapshot.Devices[j].Vendor {
|
||||
return snapshot.Devices[i].Vendor < snapshot.Devices[j].Vendor
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
if snapshot.Devices[i].NPUIndex != snapshot.Devices[j].NPUIndex {
|
||||
return snapshot.Devices[i].NPUIndex < snapshot.Devices[j].NPUIndex
|
||||
}
|
||||
if snapshot.Devices[i].ChipIndex != snapshot.Devices[j].ChipIndex {
|
||||
return snapshot.Devices[i].ChipIndex < snapshot.Devices[j].ChipIndex
|
||||
}
|
||||
return snapshot.Devices[i].Index < snapshot.Devices[j].Index
|
||||
})
|
||||
for _, item := range snapshot.Devices {
|
||||
optionType := "gpu"
|
||||
if item.Kind == accelerator.KindXPU {
|
||||
optionType = "xpu"
|
||||
}
|
||||
chartHide := dto.GPUChartHide{
|
||||
ProductName: item.Label,
|
||||
Type: optionType,
|
||||
GPU: !item.Capabilities.Utilization,
|
||||
Memory: !item.Capabilities.Memory,
|
||||
Power: !item.Capabilities.Power,
|
||||
PowerLimit: !item.Capabilities.PowerLimit,
|
||||
Temperature: !item.Capabilities.Temperature,
|
||||
Speed: !item.Capabilities.FanSpeed,
|
||||
}
|
||||
data.ChartHide = append(data.ChartHide, chartHide)
|
||||
data.Options = append(data.Options, chartHide.ProductName)
|
||||
seen[key] = true
|
||||
data.ChartHide = append(data.ChartHide, dto.GPUChartHide{DeviceID: device.DeviceID, ProductName: device.ProductName, Type: device.DeviceType, Legacy: device.DeviceID == ""})
|
||||
data.Options = append(data.Options, device.ProductName)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) {
|
||||
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
req.StartTime = req.StartTime.In(loc)
|
||||
req.EndTime = req.EndTime.In(loc)
|
||||
var data dto.MonitorGPUData
|
||||
gpuList, err := monitorRepo.GetGPU(repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByProductName(req.ProductName))
|
||||
if req.StartTime.IsZero() || req.EndTime.IsZero() || !req.EndTime.After(req.StartTime) {
|
||||
return data, fmt.Errorf("invalid GPU history time range")
|
||||
}
|
||||
if req.DeviceID == "" && req.ProductName == "" {
|
||||
return data, fmt.Errorf("GPU history requires a device")
|
||||
}
|
||||
if req.Aggregation != "" && req.Aggregation != "avg" && req.Aggregation != "max" {
|
||||
return data, fmt.Errorf("invalid GPU history aggregation")
|
||||
}
|
||||
loc, err := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
for _, gpu := range gpuList {
|
||||
data.Date = append(data.Date, gpu.CreatedAt)
|
||||
data.GPUValue = append(data.GPUValue, gpu.GPUUtil)
|
||||
data.TemperatureValue = append(data.TemperatureValue, gpu.Temperature)
|
||||
data.PowerUsed = append(data.PowerUsed, gpu.PowerDraw)
|
||||
data.PowerTotal = append(data.PowerTotal, gpu.MaxPowerLimit)
|
||||
if gpu.MaxPowerLimit != 0 {
|
||||
data.PowerPercent = append(data.PowerPercent, gpu.PowerDraw/gpu.MaxPowerLimit*100)
|
||||
} else {
|
||||
data.PowerPercent = append(data.PowerPercent, float64(0))
|
||||
req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc)
|
||||
opts := []repo.DBOption{repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByGPUDevice(req.DeviceID, req.ProductName, req.Legacy)}
|
||||
data.SampleCount, err = monitorRepo.CountGPU(opts...)
|
||||
if err != nil || data.SampleCount == 0 {
|
||||
return data, err
|
||||
}
|
||||
if data.SampleCount > 1200 {
|
||||
seconds := req.EndTime.Unix() - req.StartTime.Unix() + 1
|
||||
data.BucketSeconds = (seconds + 599) / 600
|
||||
}
|
||||
points, err := monitorRepo.GetGPUHistory(req.StartTime, data.BucketSeconds, req.Aggregation, opts...)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
samples := make([]repo.GPUHistoryPoint, 0, len(points))
|
||||
if data.BucketSeconds > 0 {
|
||||
next := 0
|
||||
for bucket := int64(0); bucket <= (req.EndTime.Unix()-req.StartTime.Unix())/data.BucketSeconds; bucket++ {
|
||||
point := repo.GPUHistoryPoint{}
|
||||
if next < len(points) && points[next].Bucket == bucket {
|
||||
point = points[next]
|
||||
next++
|
||||
}
|
||||
point.CreatedAt = time.Unix(req.StartTime.Unix()+bucket*data.BucketSeconds, 0).In(loc)
|
||||
if bucket == 0 {
|
||||
point.CreatedAt = req.StartTime
|
||||
}
|
||||
samples = append(samples, point)
|
||||
}
|
||||
|
||||
data.MemoryTotal = append(data.MemoryTotal, gpu.MemTotal)
|
||||
data.MemoryUsed = append(data.MemoryUsed, gpu.MemUsed)
|
||||
if gpu.MemTotal != 0 {
|
||||
data.MemoryPercent = append(data.MemoryPercent, gpu.MemUsed/gpu.MemTotal*100)
|
||||
} else {
|
||||
data.MemoryPercent = append(data.MemoryPercent, float64(0))
|
||||
} else {
|
||||
for i, point := range points {
|
||||
if i > 0 && points[i-1].IntervalSeconds > 0 {
|
||||
interval := time.Duration(points[i-1].IntervalSeconds) * time.Second
|
||||
if point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
|
||||
samples = append(samples, repo.GPUHistoryPoint{MonitorGPU: model.MonitorGPU{BaseModel: model.BaseModel{CreatedAt: points[i-1].CreatedAt.Add(interval)}}})
|
||||
}
|
||||
}
|
||||
samples = append(samples, point)
|
||||
}
|
||||
var process []dto.GPUProcess
|
||||
if err := json.Unmarshal([]byte(gpu.Processes), &process); err == nil {
|
||||
data.ProcessCount = append(data.ProcessCount, len(process))
|
||||
data.GPUProcesses = append(data.GPUProcesses, process)
|
||||
} else {
|
||||
data.ProcessCount = append(data.ProcessCount, 0)
|
||||
data.GPUProcesses = append(data.GPUProcesses, []dto.GPUProcess{})
|
||||
}
|
||||
for _, point := range samples {
|
||||
data.Date = append(data.Date, point.CreatedAt)
|
||||
data.MemoryActivity = append(data.MemoryActivity, point.MemoryActivity)
|
||||
data.EncoderUtil = append(data.EncoderUtil, point.EncoderUtil)
|
||||
data.DecoderUtil = append(data.DecoderUtil, point.DecoderUtil)
|
||||
data.JPEGUtil = append(data.JPEGUtil, point.JPEGUtil)
|
||||
data.OFAUtil = append(data.OFAUtil, point.OFAUtil)
|
||||
data.MediaUtil = append(data.MediaUtil, point.MediaUtil)
|
||||
data.ComputeUtil = append(data.ComputeUtil, point.ComputeUtil)
|
||||
data.CopyUtil = append(data.CopyUtil, point.CopyUtil)
|
||||
data.HotspotTemperature = append(data.HotspotTemperature, point.HotspotTemperature)
|
||||
data.FanRPM = append(data.FanRPM, point.FanRPM)
|
||||
data.AICPUUtil = append(data.AICPUUtil, point.AICPUUtil)
|
||||
data.CtrlCPUUtil = append(data.CtrlCPUUtil, point.CtrlCPUUtil)
|
||||
data.DDRUsed = append(data.DDRUsed, point.DDRUsed)
|
||||
data.DDRTotal = append(data.DDRTotal, point.DDRTotal)
|
||||
data.HBMUsed = append(data.HBMUsed, point.HBMUsed)
|
||||
data.HBMTotal = append(data.HBMTotal, point.HBMTotal)
|
||||
data.DDRBandwidth = append(data.DDRBandwidth, point.DDRBandwidth)
|
||||
data.HBMBandwidth = append(data.HBMBandwidth, point.HBMBandwidth)
|
||||
data.MemoryBandwidth = append(data.MemoryBandwidth, point.MemoryBandwidth)
|
||||
data.MediaFrequency = append(data.MediaFrequency, point.MediaFrequency)
|
||||
data.HugepagesUsed = append(data.HugepagesUsed, point.HugepagesUsed)
|
||||
data.HugepagesTotal = append(data.HugepagesTotal, point.HugepagesTotal)
|
||||
data.GPUValue = append(data.GPUValue, point.GPUUtil)
|
||||
data.TemperatureValue = append(data.TemperatureValue, point.Temperature)
|
||||
data.MemoryTemperatureValue = append(data.MemoryTemperatureValue, point.MemoryTemperature)
|
||||
data.PowerUsed = append(data.PowerUsed, point.PowerDraw)
|
||||
data.PowerTotal = append(data.PowerTotal, point.MaxPowerLimit)
|
||||
data.PowerPercent = append(data.PowerPercent, point.PowerPercent)
|
||||
data.MemoryPercent = append(data.MemoryPercent, point.MemoryPercent)
|
||||
data.MemoryTotal = append(data.MemoryTotal, point.MemTotal)
|
||||
data.MemoryUsed = append(data.MemoryUsed, point.MemUsed)
|
||||
data.SpeedValue = append(data.SpeedValue, point.FanSpeed)
|
||||
data.FrequencyValue = append(data.FrequencyValue, point.Frequency)
|
||||
data.MemoryFrequencyValue = append(data.MemoryFrequencyValue, point.MemoryFrequency)
|
||||
data.ProcessCount = append(data.ProcessCount, point.ProcessCount)
|
||||
var processes []dto.GPUProcess
|
||||
if data.BucketSeconds == 0 && point.ProcessCount != nil {
|
||||
_ = json.Unmarshal([]byte(point.Processes), &processes)
|
||||
}
|
||||
data.SpeedValue = append(data.SpeedValue, gpu.FanSpeed)
|
||||
data.GPUProcesses = append(data.GPUProcesses, processes)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -307,7 +327,6 @@ func (m *MonitorService) CleanData() error {
|
||||
}
|
||||
|
||||
func (m *MonitorService) Run() {
|
||||
saveAcceleratorDataToDB()
|
||||
var itemModel model.MonitorBase
|
||||
totalPercent, _ := cpu.Percent(3*time.Second, false)
|
||||
if len(totalPercent) == 1 {
|
||||
@@ -343,6 +362,7 @@ func (m *MonitorService) Run() {
|
||||
|
||||
m.loadDiskIO()
|
||||
m.loadNetIO()
|
||||
m.saveGPUData()
|
||||
|
||||
MonitorStoreDays, err := settingRepo.Get(settingRepo.WithByKey("MonitorStoreDays"))
|
||||
if err != nil {
|
||||
@@ -602,7 +622,62 @@ func StartMonitor(removeBefore bool, interval string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveAcceleratorDataToDB() {
|
||||
func loadGPUOptions(snapshot *accelerator.Snapshot) dto.MonitorGPUOptions {
|
||||
var data dto.MonitorGPUOptions
|
||||
hasGPUOrNPU := false
|
||||
hasXPU := false
|
||||
for _, item := range snapshot.Devices {
|
||||
if item.Kind == accelerator.KindXPU {
|
||||
hasXPU = true
|
||||
} else {
|
||||
hasGPUOrNPU = true
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case hasGPUOrNPU && hasXPU:
|
||||
data.GPUType = "mixed"
|
||||
case hasXPU:
|
||||
data.GPUType = "xpu"
|
||||
case hasGPUOrNPU:
|
||||
data.GPUType = "gpu"
|
||||
}
|
||||
|
||||
sort.Slice(snapshot.Devices, func(i, j int) bool {
|
||||
if snapshot.Devices[i].Kind != snapshot.Devices[j].Kind {
|
||||
return snapshot.Devices[i].Kind < snapshot.Devices[j].Kind
|
||||
}
|
||||
if snapshot.Devices[i].Vendor != snapshot.Devices[j].Vendor {
|
||||
return snapshot.Devices[i].Vendor < snapshot.Devices[j].Vendor
|
||||
}
|
||||
if snapshot.Devices[i].NPUIndex != snapshot.Devices[j].NPUIndex {
|
||||
return snapshot.Devices[i].NPUIndex < snapshot.Devices[j].NPUIndex
|
||||
}
|
||||
if snapshot.Devices[i].ChipIndex != snapshot.Devices[j].ChipIndex {
|
||||
return snapshot.Devices[i].ChipIndex < snapshot.Devices[j].ChipIndex
|
||||
}
|
||||
return snapshot.Devices[i].Index < snapshot.Devices[j].Index
|
||||
})
|
||||
for _, item := range snapshot.Devices {
|
||||
chartHide := dto.GPUChartHide{
|
||||
DeviceID: item.ID,
|
||||
ProductName: item.Label,
|
||||
Type: string(item.Kind),
|
||||
}
|
||||
data.ChartHide = append(data.ChartHide, chartHide)
|
||||
data.Options = append(data.Options, chartHide.ProductName)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (m *MonitorService) saveGPUData() {
|
||||
status, err := settingRepo.GetValueByKey("MonitorStatus")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load monitor status failed: %v", err)
|
||||
return
|
||||
}
|
||||
if status != constant.StatusEnable {
|
||||
return
|
||||
}
|
||||
exist, client := accelerator.New()
|
||||
if !exist {
|
||||
return
|
||||
@@ -615,27 +690,64 @@ func saveAcceleratorDataToDB() {
|
||||
if warning := snapshot.Warning(); warning != nil {
|
||||
global.LOG.Warnf("load accelerator monitor data partially failed, err: %v", warning)
|
||||
}
|
||||
intervalSeconds := 0
|
||||
if setting, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval")); err == nil {
|
||||
intervalSeconds, _ = strconv.Atoi(setting.Value)
|
||||
}
|
||||
list := make([]model.MonitorGPU, 0, len(snapshot.Devices))
|
||||
for _, device := range snapshot.Devices {
|
||||
list = append(list, newMonitorGPU(device))
|
||||
item := newMonitorGPU(device)
|
||||
item.CreatedAt = snapshot.Info.CollectedAt
|
||||
item.IntervalSeconds = intervalSeconds
|
||||
list = append(list, item)
|
||||
}
|
||||
if err := repo.NewIMonitorRepo().BatchCreateMonitorGPU(list); err != nil {
|
||||
if err := monitorRepo.BatchCreateMonitorGPU(list); err != nil {
|
||||
global.LOG.Errorf("batch create accelerator monitor data failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newMonitorGPU(device accelerator.Device) model.MonitorGPU {
|
||||
item := model.MonitorGPU{
|
||||
ProductName: device.Label,
|
||||
GPUUtil: device.Metrics.Utilization.ValueOrZero(),
|
||||
Temperature: device.Metrics.Temperature.ValueOrZero(),
|
||||
PowerDraw: device.Metrics.Power.ValueOrZero(),
|
||||
MaxPowerLimit: device.Metrics.PowerLimit.ValueOrZero(),
|
||||
MemUsed: device.Metrics.MemoryUsed.ValueOrZero(),
|
||||
MemTotal: device.Metrics.MemoryTotal.ValueOrZero(),
|
||||
FanSpeed: int(device.Metrics.FanSpeed.ValueOrZero()),
|
||||
MemoryUtil: device.Metrics.MemoryUtil.Value,
|
||||
MemoryActivity: device.Metrics.MemoryActivity.Value,
|
||||
EncoderUtil: device.Metrics.EncoderUtil.Value,
|
||||
DecoderUtil: device.Metrics.DecoderUtil.Value,
|
||||
JPEGUtil: device.Metrics.JPEGUtil.Value,
|
||||
OFAUtil: device.Metrics.OFAUtil.Value,
|
||||
MediaUtil: device.Metrics.MediaUtil.Value,
|
||||
ComputeUtil: device.Metrics.ComputeUtil.Value,
|
||||
CopyUtil: device.Metrics.CopyUtil.Value,
|
||||
HotspotTemperature: device.Metrics.HotspotTemperature.Value,
|
||||
FanRPM: device.Metrics.FanRPM.Value,
|
||||
AICPUUtil: device.Metrics.AICPUUtil.Value,
|
||||
CtrlCPUUtil: device.Metrics.CtrlCPUUtil.Value,
|
||||
DDRUsed: device.Metrics.DDRUsed.Value,
|
||||
DDRTotal: device.Metrics.DDRTotal.Value,
|
||||
HBMUsed: device.Metrics.HBMUsed.Value,
|
||||
HBMTotal: device.Metrics.HBMTotal.Value,
|
||||
DDRBandwidth: device.Metrics.DDRBandwidth.Value,
|
||||
HBMBandwidth: device.Metrics.HBMBandwidth.Value,
|
||||
MemoryBandwidth: device.Metrics.MemoryBandwidth.Value,
|
||||
MediaFrequency: device.Metrics.MediaFrequency.Value,
|
||||
HugepagesUsed: device.Metrics.HugepagesUsed.Value,
|
||||
HugepagesTotal: device.Metrics.HugepagesTotal.Value,
|
||||
|
||||
ProductName: device.Label,
|
||||
DeviceID: device.ID,
|
||||
DeviceType: string(device.Kind),
|
||||
ProcessStatus: device.ProcessStatus,
|
||||
Frequency: device.Metrics.Frequency.Value,
|
||||
MemoryFrequency: device.Metrics.MemoryFrequency.Value,
|
||||
MemoryTemperature: device.Metrics.MemoryTemperature.Value,
|
||||
GPUUtil: device.Metrics.Utilization.Value,
|
||||
Temperature: device.Metrics.Temperature.Value,
|
||||
PowerDraw: device.Metrics.Power.Value,
|
||||
MaxPowerLimit: device.Metrics.PowerLimit.Value,
|
||||
MemUsed: device.Metrics.MemoryUsed.Value,
|
||||
MemTotal: device.Metrics.MemoryTotal.Value,
|
||||
FanSpeed: device.Metrics.FanSpeed.Value,
|
||||
}
|
||||
if len(device.Processes) == 0 {
|
||||
if device.ProcessStatus != "ok" {
|
||||
return item
|
||||
}
|
||||
processes := make([]dto.GPUProcess, 0, len(device.Processes))
|
||||
|
||||
+12
-2
@@ -154,7 +154,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
|
||||
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
|
||||
logger := logrus.New()
|
||||
logger.SetFormatter(&SimpleFormatter{})
|
||||
logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, constant.FilePerm)
|
||||
logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY|os.O_APPEND, constant.FilePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||||
}
|
||||
@@ -283,8 +283,18 @@ func (t *Task) updateTask(task *model.Task) {
|
||||
_ = t.taskRepo.Update(context.Background(), task)
|
||||
}
|
||||
|
||||
func (t *Task) Execute() error {
|
||||
// Prepare makes a task visible before dispatching it to a background worker.
|
||||
func (t *Task) Prepare() error {
|
||||
if err := t.taskRepo.Save(context.Background(), t.Task); err != nil {
|
||||
_ = t.logFile.Close()
|
||||
global.RemoveTaskCancel(t.TaskID)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) Execute() error {
|
||||
if err := t.Prepare(); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
|
||||
@@ -644,6 +644,13 @@
|
||||
"formatZH": "创建容器网络 name",
|
||||
"formatEN": "create container network [name]"
|
||||
},
|
||||
"/containers/network/clean": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "清理未使用的容器网络",
|
||||
"formatEN": "Clean unused container networks"
|
||||
},
|
||||
"/containers/network/del": {
|
||||
"bodyKeys": [
|
||||
"names"
|
||||
@@ -4028,12 +4035,21 @@
|
||||
},
|
||||
"/runtimes/operate": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
"ID"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "操作运行环境 [id]",
|
||||
"formatEN": "Operate runtime [id]"
|
||||
"beforeFunctions": [
|
||||
{
|
||||
"input_column": "id",
|
||||
"input_value": "ID",
|
||||
"isList": false,
|
||||
"db": "runtimes",
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"formatZH": "操作运行环境 [name]",
|
||||
"formatEN": "Operate runtime [name]"
|
||||
},
|
||||
"/runtimes/php/config": {
|
||||
"bodyKeys": [
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: "Panel {{ .node }}{{ .ip }}: {{ .msg }}. Log in to view details."
|
||||
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} nodes are abnormal. Log in to view details."
|
||||
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }}: {{ .num }} licenses are abnormal. Log in to view details."
|
||||
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }}: abnormal {{ .name }} login from {{ .loginIp }}. Log in to view details."
|
||||
CronJobSuccessAlert: "Panel {{ .node }}{{ .ip }}: scheduled task {{ .name }} completed successfully. Log in to view details."
|
||||
|
||||
# disk
|
||||
DeviceNotFound: "Device {{ .name }} not found"
|
||||
@@ -730,3 +731,13 @@ FileTaskRename: 'Target filename: {{ .name }}'
|
||||
ErrFirewallBackendCleanupRequired: "The current backend {{ .current }} still contains 1Panel rules. Clean it up before switching to {{ .target }}."
|
||||
ErrDockerIPv4ForwardingDisabled: "IPv4 forwarding is disabled. Set net.ipv4.ip_forward=1 before using Docker's firewall backend."
|
||||
ErrFirewallRuleSavedApplyFailed: "The new rule configuration was saved, but applying it to the firewall failed. Retry by synchronizing: {{ .detail }}"
|
||||
|
||||
NetworkCleanupDeleted: "Deleted network [{{ .name }}] ({{ .id }})"
|
||||
NetworkCleanupProtected: "Skipped network [{{ .name }}] ({{ .id }}): reserved network"
|
||||
NetworkCleanupConnected: "Skipped network [{{ .name }}] ({{ .id }}): containers connected or network in use; not deleted"
|
||||
NetworkCleanupUnsupported: "Skipped network [{{ .name }}] ({{ .id }}): special network"
|
||||
NetworkCleanupGone: "Skipped network [{{ .name }}] ({{ .id }}): already removed"
|
||||
NetworkCleanupInspectFailed: "Failed to inspect network [{{ .name }}] ({{ .id }}); not deleted"
|
||||
NetworkCleanupRemoveFailed: "Failed to remove network [{{ .name }}] ({{ .id }})"
|
||||
NetworkCleanupSummary: "Network cleanup finished: deleted {{ .deleted }}, skipped {{ .skipped }}, failed {{ .failed }}"
|
||||
NetworkCleanupPartialFailure: "Some networks could not be cleaned; see the task log"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el pa
|
||||
NodeExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles.'
|
||||
LicenseExceptionAlert: 'Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles.'
|
||||
SSHAndPanelLoginAlert: 'Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: la tarea programada {{ .name }} se completó correctamente. Inicie sesión para ver los detalles.'
|
||||
|
||||
# disco
|
||||
DeviceNotFound: 'Dispositivo {{ .name }} no encontrado'
|
||||
|
||||
@@ -649,6 +649,8 @@ CommonAlert: "پنل {{ .node }}{{ .ip }}: {{ .msg }}. برای مشاهده ج
|
||||
NodeExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} گره غیرعادی هستند. برای مشاهده جزئیات وارد شوید."
|
||||
LicenseExceptionAlert: "پنل {{ .node }}{{ .ip }}: {{ .num }} مجوز غیرعادی است. برای مشاهده جزئیات وارد شوید."
|
||||
SSHAndPanelLoginAlert: "پنل {{ .node }}{{ .ip }}: ورود غیرعادی {{ .name }} از {{ .loginIp }}. برای مشاهده جزئیات وارد شوید."
|
||||
CronJobSuccessAlert: "پنل {{ .node }}{{ .ip }}: وظیفه زمانبندیشده {{ .name }} با موفقیت تکمیل شد. برای مشاهده جزئیات وارد شوید."
|
||||
|
||||
|
||||
# دیسک
|
||||
DeviceNotFound: "دستگاه {{ .name }} یافت نشد"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細
|
||||
NodeExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインして'
|
||||
LicenseExceptionAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインして'
|
||||
SSHAndPanelLoginAlert: 'あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインして'
|
||||
CronJobSuccessAlert: 'パネル {{ .node }}{{ .ip }}: スケジュールタスク {{ .name }} が正常に完了しました。詳細はパネルにログインして確認してください。'
|
||||
|
||||
# ディスク
|
||||
DeviceNotFound: 'デバイス {{ .name }} が見つかりません'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내
|
||||
NodeExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
LicenseExceptionAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
SSHAndPanelLoginAlert: '귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오.'
|
||||
CronJobSuccessAlert: '패널 {{ .node }}{{ .ip }}: 예약 작업 {{ .name }}이(가) 성공적으로 완료되었습니다. 로그인하여 자세한 내용을 확인하십시오.'
|
||||
|
||||
# 디스크
|
||||
DeviceNotFound: '장치 {{ .name }} 을(를) 찾을 수 없습니다'
|
||||
|
||||
@@ -639,6 +639,7 @@ CommonAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: {{ .msg }}. ເ
|
||||
NodeExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໂນດ {{ .num }} ແຫ່ງຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
LicenseExceptionAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ໃບອະນຸຍາດ {{ .num }} ສະບັບຜິດປົກກະຕິ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
SSHAndPanelLoginAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ມີການເຂົ້າສູ່ລະບົບ {{ .name }} ທີ່ຜິດປົກກະຕິຈາກ {{ .loginIp }}. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
CronJobSuccessAlert: "ແຜງຄວບຄຸມ {{ .node }}{{ .ip }}: ວຽກທີ່ຕັ້ງເວລາ {{ .name }} ສຳເລັດແລ້ວ. ເຂົ້າສູ່ລະບົບເພື່ອເບິ່ງລາຍລະອຽດ."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "ບໍ່ພົບອຸປະກອນ {{ .name }}"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke pan
|
||||
NodeExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||
LicenseExceptionAlert: 'Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||
SSHAndPanelLoginAlert: 'Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: tugas berjadual {{ .name }} berjaya diselesaikan. Log masuk untuk melihat butiran.'
|
||||
|
||||
# cakera
|
||||
DeviceNotFound: 'Peranti {{ .name }} tidak ditemui'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel
|
||||
NodeExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes.'
|
||||
LicenseExceptionAlert: 'Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes.'
|
||||
SSHAndPanelLoginAlert: 'Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes.'
|
||||
CronJobSuccessAlert: 'Painel {{ .node }}{{ .ip }}: a tarefa agendada {{ .name }} foi concluída com sucesso. Faça login para ver os detalhes.'
|
||||
|
||||
# disco
|
||||
DeviceNotFound: 'Dispositivo {{ .name }} não encontrado'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдит
|
||||
NodeExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей.'
|
||||
LicenseExceptionAlert: 'Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей.'
|
||||
SSHAndPanelLoginAlert: 'Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей.'
|
||||
CronJobSuccessAlert: 'Панель {{ .node }}{{ .ip }}: запланированная задача {{ .name }} успешно завершена. Войдите для просмотра подробностей.'
|
||||
|
||||
# диск
|
||||
DeviceNotFound: 'Устройство {{ .name }} не найдено'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için panelin
|
||||
NodeExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın.'
|
||||
LicenseExceptionAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın.'
|
||||
SSHAndPanelLoginAlert: 'Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın.'
|
||||
CronJobSuccessAlert: 'Panel {{ .node }}{{ .ip }}: zamanlanmış görev {{ .name }} başarıyla tamamlandı. Ayrıntıları görmek için giriş yapın.'
|
||||
|
||||
# disk
|
||||
DeviceNotFound: 'Cihaz {{ .name }} bulunamadı'
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入
|
||||
NodeExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板檢視。'
|
||||
LicenseExceptionAlert: '您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板檢視。'
|
||||
SSHAndPanelLoginAlert: '您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板檢視。'
|
||||
CronJobSuccessAlert: '您的 {{ .node }}{{ .ip }} 面板,排程任務 {{ .name }} 執行成功,詳情請登入面板檢視。'
|
||||
|
||||
# 磁碟
|
||||
DeviceNotFound: '裝置 {{ .name }} 未找到'
|
||||
@@ -730,3 +731,13 @@ FileTaskRename: '目標檔名:{{ .name }}'
|
||||
ErrFirewallBackendCleanupRequired: "目前後端 {{ .current }} 中仍有 1Panel 規則,請先清理後再切換至 {{ .target }}。"
|
||||
ErrDockerIPv4ForwardingDisabled: "IPv4 轉送尚未啟用,請先設定 net.ipv4.ip_forward=1,再使用 Docker 防火牆後端。"
|
||||
ErrFirewallRuleSavedApplyFailed: "新規則設定已儲存,但套用至防火牆失敗。可透過同步重試:{{ .detail }}"
|
||||
|
||||
NetworkCleanupDeleted: "網路 [{{ .name }}] ({{ .id }}) 已刪除"
|
||||
NetworkCleanupProtected: "跳過網路 [{{ .name }}] ({{ .id }}):預設保留網路,未刪除"
|
||||
NetworkCleanupConnected: "跳過網路 [{{ .name }}] ({{ .id }}):仍有容器連接或正在使用,未刪除"
|
||||
NetworkCleanupUnsupported: "跳過網路 [{{ .name }}] ({{ .id }}):特殊網路,未刪除"
|
||||
NetworkCleanupGone: "跳過網路 [{{ .name }}] ({{ .id }}):網路已不存在"
|
||||
NetworkCleanupInspectFailed: "網路 [{{ .name }}] ({{ .id }}) 檢查失敗,未刪除"
|
||||
NetworkCleanupRemoveFailed: "網路 [{{ .name }}] ({{ .id }}) 刪除失敗"
|
||||
NetworkCleanupSummary: "網路清理完成:已刪除 {{ .deleted }},已跳過 {{ .skipped }},失敗 {{ .failed }}"
|
||||
NetworkCleanupPartialFailure: "部分網路清理失敗,請查看任務日誌"
|
||||
|
||||
@@ -649,6 +649,7 @@ CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},请登录面板
|
||||
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,请登录面板查看详情。"
|
||||
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,请登录面板查看详情。"
|
||||
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板 {{ .name }} 登录 {{ .loginIp }} 异常,请登录面板查看详情。"
|
||||
CronJobSuccessAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行成功,请登录面板查看详情。"
|
||||
|
||||
# 磁盘
|
||||
DeviceNotFound: "设备 {{ .name }} 未找到"
|
||||
@@ -730,3 +731,13 @@ FileTaskRename: '目标文件名:{{ .name }}'
|
||||
ErrFirewallBackendCleanupRequired: "当前后端 {{ .current }} 中仍有 1Panel 规则,请先清理后再切换到 {{ .target }}。"
|
||||
ErrDockerIPv4ForwardingDisabled: "IPv4 转发未开启,请先设置 net.ipv4.ip_forward=1,再使用 Docker 防火墙后端。"
|
||||
ErrFirewallRuleSavedApplyFailed: "新规则配置已保存,但应用到防火墙失败。可通过同步重试:{{ .detail }}"
|
||||
|
||||
NetworkCleanupDeleted: "网络 [{{ .name }}] ({{ .id }}) 已删除"
|
||||
NetworkCleanupProtected: "跳过网络 [{{ .name }}] ({{ .id }}):默认保留网络,未删除"
|
||||
NetworkCleanupConnected: "跳过网络 [{{ .name }}] ({{ .id }}):仍有容器连接或正在使用,未删除"
|
||||
NetworkCleanupUnsupported: "跳过网络 [{{ .name }}] ({{ .id }}):特殊网络,未删除"
|
||||
NetworkCleanupGone: "跳过网络 [{{ .name }}] ({{ .id }}):网络已不存在"
|
||||
NetworkCleanupInspectFailed: "网络 [{{ .name }}] ({{ .id }}) 检查失败,未删除"
|
||||
NetworkCleanupRemoveFailed: "网络 [{{ .name }}] ({{ .id }}) 删除失败"
|
||||
NetworkCleanupSummary: "网络清理完成:已删除 {{ .deleted }},已跳过 {{ .skipped }},失败 {{ .failed }}"
|
||||
NetworkCleanupPartialFailure: "部分网络清理失败,请查看任务日志"
|
||||
|
||||
@@ -111,6 +111,7 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.SimplifyFirewallRulePolicy,
|
||||
migrations.AddDockerPortGuardReadOnly,
|
||||
migrations.MigrateFirewallPortWhitelistSources,
|
||||
migrations.AddAcceleratorMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1908,3 +1908,10 @@ var AddDockerPortGuardReadOnly = &gormigrate.Migration{
|
||||
return tx.Migrator().CreateIndex(&model.DockerPortGuardPolicy{}, "idx_docker_port_guard_endpoint")
|
||||
},
|
||||
}
|
||||
|
||||
var AddAcceleratorMetrics = &gormigrate.Migration{
|
||||
ID: "20260928-accelerator-vendor-metrics",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
|
||||
baRouter.GET("/network", baseApi.ListNetwork)
|
||||
baRouter.POST("/network/del", baseApi.DeleteNetwork)
|
||||
baRouter.POST("/network/clean", baseApi.CleanNetworks)
|
||||
baRouter.POST("/network/search", baseApi.SearchNetwork)
|
||||
baRouter.POST("/network", baseApi.CreateNetwork)
|
||||
baRouter.GET("/volume", baseApi.ListVolume)
|
||||
|
||||
@@ -43,6 +43,7 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
||||
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
||||
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
||||
cmdRouter.GET("/redis/cli/status", baseApi.LoadRedisCliStatus)
|
||||
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
||||
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
||||
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||
@@ -52,6 +53,11 @@ func (c Client) LoadInfo() (*Info, error) {
|
||||
}
|
||||
|
||||
func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
results := make([]providerResult, len(c.providers))
|
||||
var wg sync.WaitGroup
|
||||
for index, item := range c.providers {
|
||||
@@ -66,8 +72,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snapshot := &Snapshot{DriverVersions: make(map[string]string)}
|
||||
snapshot := &Snapshot{}
|
||||
var (
|
||||
errs []error
|
||||
active []*ProviderSnapshot
|
||||
@@ -78,6 +87,11 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
errs = append(errs, result.err)
|
||||
continue
|
||||
}
|
||||
if result.snapshot != nil {
|
||||
for _, warning := range result.snapshot.Warnings {
|
||||
errs = append(errs, errors.New(warning))
|
||||
}
|
||||
}
|
||||
if result.snapshot == nil || len(result.snapshot.Devices) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -90,11 +104,8 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
if item.CudaVersion != "" {
|
||||
snapshot.Info.CudaVersion = item.CudaVersion
|
||||
}
|
||||
if item.DriverVersion != "" {
|
||||
snapshot.DriverVersions[item.Type] = item.DriverVersion
|
||||
if item.Type == "xpu" {
|
||||
xpuVersion = item.DriverVersion
|
||||
}
|
||||
if item.Type == "xpu" {
|
||||
xpuVersion = item.DriverVersion
|
||||
}
|
||||
}
|
||||
snapshot.Warnings = append(snapshot.Warnings, errs...)
|
||||
@@ -102,6 +113,10 @@ func (c Client) Collect(ctx context.Context) (*Snapshot, error) {
|
||||
return nil, fmt.Errorf("calling accelerator monitoring tools failed: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
snapshot.Info.CollectedAt = time.Now()
|
||||
for _, err := range errs {
|
||||
snapshot.Info.Warnings = append(snapshot.Info.Warnings, err.Error())
|
||||
}
|
||||
snapshot.Info.XPUDriverVersion = xpuVersion
|
||||
snapshot.Info.Type, snapshot.Info.DriverVersion = mergeProviderMetadata(active)
|
||||
return snapshot, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package accelerator
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -18,11 +19,11 @@ func metric(display, unit string) Metric {
|
||||
return result
|
||||
}
|
||||
parsed, err := strconv.ParseFloat(matched[1], 64)
|
||||
if err != nil {
|
||||
if err != nil || math.IsNaN(parsed) || math.IsInf(parsed, 0) || (parsed < 0 && unit != "°C") {
|
||||
return result
|
||||
}
|
||||
normalized, ok := convertMetricUnit(parsed, matched[2], unit)
|
||||
if !ok {
|
||||
if !ok || math.IsNaN(normalized) || math.IsInf(normalized, 0) || (unit == "%" && normalized > 100) {
|
||||
return result
|
||||
}
|
||||
result.Value = &normalized
|
||||
|
||||
@@ -12,26 +12,38 @@ import (
|
||||
|
||||
func normalizeGPU(item *gpu.Device) Device {
|
||||
metrics := Metrics{
|
||||
Utilization: metric(item.GPUUtil, "%"),
|
||||
Temperature: metric(item.Temperature, "°C"),
|
||||
Power: metric(item.PowerDraw, "W"),
|
||||
PowerLimit: metric(item.MaxPowerLimit, "W"),
|
||||
MemoryUsed: memoryMetric(item.MemUsed),
|
||||
MemoryTotal: memoryMetric(item.MemTotal),
|
||||
FanSpeed: metric(item.FanSpeed, "%"),
|
||||
MemoryActivity: metric(item.MemoryActivity, "%"),
|
||||
EncoderUtil: metric(item.EncoderUtil, "%"),
|
||||
DecoderUtil: metric(item.DecoderUtil, "%"),
|
||||
JPEGUtil: metric(item.JPEGUtil, "%"),
|
||||
OFAUtil: metric(item.OFAUtil, "%"),
|
||||
MediaUtil: metric(item.MediaUtil, "%"),
|
||||
HotspotTemperature: metric(item.HotspotTemperature, "°C"),
|
||||
FanRPM: metric(item.FanRPM, "RPM"),
|
||||
MediaFrequency: metric(item.MediaFrequency, "MHz"),
|
||||
Utilization: metric(item.GPUUtil, "%"),
|
||||
Temperature: metric(item.Temperature, "°C"),
|
||||
MemoryTemperature: metric(item.MemoryTemperature, "°C"),
|
||||
Power: metric(item.PowerDraw, "W"),
|
||||
PowerLimit: metric(item.PowerLimit, "W"),
|
||||
Frequency: metric(item.Frequency, "MHz"),
|
||||
MemoryFrequency: metric(item.MemoryFrequency, "MHz"),
|
||||
MemoryUsed: memoryMetric(item.MemUsed),
|
||||
MemoryTotal: memoryMetric(item.MemTotal),
|
||||
FanSpeed: metric(item.FanSpeed, "%"),
|
||||
}
|
||||
device := Device{
|
||||
ID: stableID(item.Type, item.BusID, strconv.FormatUint(uint64(item.Index), 10)),
|
||||
Kind: KindGPU,
|
||||
Vendor: item.Type,
|
||||
Index: int(item.Index),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
GPU: item,
|
||||
ProcessStatus: item.ProcessStatus,
|
||||
ID: stableID(item.Type, item.UUID, stableID("pci", item.BusID, strconv.FormatUint(uint64(item.Index), 10))),
|
||||
Kind: KindGPU,
|
||||
Vendor: item.Type,
|
||||
Index: int(item.Index),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
GPU: item,
|
||||
}
|
||||
device.Capabilities = capabilities(metrics)
|
||||
for _, process := range item.Processes {
|
||||
device.Processes = append(device.Processes, Process{
|
||||
PID: process.PID,
|
||||
@@ -45,26 +57,36 @@ func normalizeGPU(item *gpu.Device) Device {
|
||||
|
||||
func normalizeNPU(item *npu.Device) Device {
|
||||
metrics := Metrics{
|
||||
Utilization: metric(item.AICore, "%"),
|
||||
Temperature: metric(item.Temperature, "°C"),
|
||||
Power: metric(item.PowerDraw, "W"),
|
||||
MemoryUsed: memoryMetric(item.MemUsed),
|
||||
MemoryTotal: memoryMetric(item.MemTotal),
|
||||
AICPUUtil: metric(item.AICPUUtil, "%"),
|
||||
CtrlCPUUtil: metric(item.CtrlCPUUtil, "%"),
|
||||
DDRBandwidth: metric(item.DDRBandwidth, "%"),
|
||||
HBMBandwidth: metric(item.HBMBandwidth, "%"),
|
||||
DDRUsed: metric(item.MemoryUsed, "MiB"),
|
||||
DDRTotal: metric(item.MemoryTotal, "MiB"),
|
||||
HBMUsed: metric(item.HBMUsed, "MiB"),
|
||||
HBMTotal: metric(item.HBMTotal, "MiB"),
|
||||
HugepagesUsed: metric(item.HugepagesUsed, "pages"),
|
||||
HugepagesTotal: metric(item.HugepagesTotal, "pages"),
|
||||
Utilization: metric(item.AICore, "%"),
|
||||
Temperature: metric(item.Temperature, "°C"),
|
||||
Power: metric(item.PowerDraw, "W"),
|
||||
MemoryUsed: memoryMetric(item.MemUsed),
|
||||
MemoryTotal: memoryMetric(item.MemTotal),
|
||||
}
|
||||
device := Device{
|
||||
ID: fmt.Sprintf("ascend:%d:%d", item.NPUIndex, item.ChipIndex),
|
||||
Kind: KindNPU,
|
||||
Vendor: "ascend",
|
||||
Index: int(item.Index),
|
||||
NPUIndex: int(item.NPUIndex),
|
||||
ChipIndex: int(item.ChipIndex),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
NPU: item,
|
||||
ProcessStatus: item.ProcessStatus,
|
||||
ID: fmt.Sprintf("%s:%d", stableID("ascend", item.BusID, strconv.FormatUint(uint64(item.NPUIndex), 10)), item.ChipIndex),
|
||||
Kind: KindNPU,
|
||||
Vendor: "ascend",
|
||||
Index: int(item.Index),
|
||||
NPUIndex: int(item.NPUIndex),
|
||||
ChipIndex: int(item.ChipIndex),
|
||||
Name: item.ProductName,
|
||||
Label: fmt.Sprintf("NPU %d / Chip %d - %s", item.NPUIndex, item.ChipIndex, item.ProductName),
|
||||
BusID: item.BusID,
|
||||
Metrics: metrics,
|
||||
NPU: item,
|
||||
}
|
||||
device.Capabilities = capabilities(metrics)
|
||||
for _, process := range item.Processes {
|
||||
device.Processes = append(device.Processes, Process{
|
||||
PID: process.PID,
|
||||
@@ -78,26 +100,32 @@ func normalizeNPU(item *npu.Device) Device {
|
||||
|
||||
func normalizeXPU(item *xpu.Device) Device {
|
||||
metrics := Metrics{
|
||||
Utilization: metric(item.Stats.GPUUtil, "%"),
|
||||
Temperature: metric(item.Stats.Temperature, "°C"),
|
||||
Power: metric(item.Stats.Power, "W"),
|
||||
MemoryUsed: memoryMetric(item.Stats.MemoryUsed),
|
||||
MemoryTotal: memoryMetric(item.Basic.Memory),
|
||||
MemoryUtil: metric(item.Stats.MemoryUtil, "%"),
|
||||
Frequency: metric(item.Stats.Frequency, "MHz"),
|
||||
MediaUtil: metric(item.Stats.MediaUtil, "%"),
|
||||
ComputeUtil: metric(item.Stats.ComputeUtil, "%"),
|
||||
CopyUtil: metric(item.Stats.CopyUtil, "%"),
|
||||
MediaFrequency: metric(item.Stats.MediaFrequency, "MHz"),
|
||||
MemoryTemperature: metric(item.Stats.MemoryTemperature, "°C"),
|
||||
MemoryBandwidth: metric(item.Stats.MemoryBandwidthUtil, "%"),
|
||||
Utilization: metric(item.Stats.GPUUtil, "%"),
|
||||
Temperature: metric(item.Stats.Temperature, "°C"),
|
||||
Power: metric(item.Stats.Power, "W"),
|
||||
MemoryUsed: memoryMetric(item.Stats.MemoryUsed),
|
||||
MemoryTotal: memoryMetric(item.Basic.Memory),
|
||||
MemoryUtil: metric(item.Stats.MemoryUtil, "%"),
|
||||
Frequency: metric(item.Stats.Frequency, "MHz"),
|
||||
}
|
||||
device := Device{
|
||||
ID: stableID("xpu", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID)),
|
||||
Kind: KindXPU,
|
||||
Vendor: item.Basic.VendorName,
|
||||
Index: item.Basic.DeviceID,
|
||||
Name: item.Basic.DeviceName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
|
||||
BusID: item.Basic.PciBdfAddress,
|
||||
Metrics: metrics,
|
||||
XPU: item,
|
||||
ProcessStatus: item.ProcessStatus,
|
||||
ID: stableID("xpu", item.Basic.UUID, stableID("pci", item.Basic.PciBdfAddress, strconv.Itoa(item.Basic.DeviceID))),
|
||||
Kind: KindXPU,
|
||||
Vendor: item.Basic.VendorName,
|
||||
Index: item.Basic.DeviceID,
|
||||
Name: item.Basic.DeviceName,
|
||||
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
|
||||
BusID: item.Basic.PciBdfAddress,
|
||||
Metrics: metrics,
|
||||
XPU: item,
|
||||
}
|
||||
device.Capabilities = capabilities(metrics)
|
||||
for _, process := range item.Processes {
|
||||
device.Processes = append(device.Processes, Process{
|
||||
PID: strconv.Itoa(process.PID),
|
||||
@@ -110,18 +138,6 @@ func normalizeXPU(item *xpu.Device) Device {
|
||||
return device
|
||||
}
|
||||
|
||||
func capabilities(metrics Metrics) Capabilities {
|
||||
return Capabilities{
|
||||
Utilization: metrics.Utilization.Available(),
|
||||
Temperature: metrics.Temperature.Available(),
|
||||
Power: metrics.Power.Available(),
|
||||
PowerLimit: metrics.PowerLimit.Available(),
|
||||
Memory: metrics.MemoryUsed.Available() || metrics.MemoryTotal.Available(),
|
||||
FanSpeed: metrics.FanSpeed.Available(),
|
||||
Frequency: metrics.Frequency.Available(),
|
||||
}
|
||||
}
|
||||
|
||||
func stableID(vendor, busID, fallback string) string {
|
||||
if busID != "" && !strings.EqualFold(busID, "N/A") {
|
||||
return vendor + ":" + busID
|
||||
|
||||
@@ -2,6 +2,7 @@ package accelerator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||
@@ -26,6 +27,7 @@ func (p gpuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
||||
}
|
||||
result := &ProviderSnapshot{
|
||||
Type: info.Type,
|
||||
Warnings: info.Warnings,
|
||||
DriverVersion: info.DriverVersion,
|
||||
CudaVersion: info.CudaVersion,
|
||||
GPUs: info.Devices,
|
||||
@@ -49,6 +51,7 @@ func (p npuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
||||
}
|
||||
result := &ProviderSnapshot{
|
||||
Type: info.Type,
|
||||
Warnings: info.Warnings,
|
||||
DriverVersion: info.DriverVersion,
|
||||
NPUs: info.Devices,
|
||||
}
|
||||
@@ -71,11 +74,24 @@ func (p xpuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
|
||||
}
|
||||
result := &ProviderSnapshot{
|
||||
Type: info.Type,
|
||||
Warnings: info.Warnings,
|
||||
DriverVersion: info.DriverVersion,
|
||||
XPUs: info.Devices,
|
||||
}
|
||||
for index := range result.XPUs {
|
||||
result.Devices = append(result.Devices, normalizeXPU(&result.XPUs[index]))
|
||||
|
||||
parent := normalizeXPU(&result.XPUs[index])
|
||||
result.Devices = append(result.Devices, parent)
|
||||
for _, tile := range result.XPUs[index].Tiles {
|
||||
raw := xpu.Device{Basic: result.XPUs[index].Basic, Stats: tile.Stats, ProcessStatus: "unavailable"}
|
||||
raw.Basic.Memory = ""
|
||||
raw.Basic.FreeMemory = ""
|
||||
device := normalizeXPU(&raw)
|
||||
device.ParentID = parent.ID
|
||||
device.ID = fmt.Sprintf("%s:tile:%d", parent.ID, tile.TileID)
|
||||
device.Label = fmt.Sprintf("%s / Tile %d", parent.Label, tile.TileID)
|
||||
result.Devices = append(result.Devices, device)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package accelerator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
|
||||
@@ -17,6 +18,9 @@ const (
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
CollectedAt time.Time `json:"collectedAt"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Type string `json:"type"`
|
||||
CudaVersion string `json:"cudaVersion"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
@@ -44,25 +48,40 @@ func (m Metric) ValueOrZero() float64 {
|
||||
}
|
||||
|
||||
type Metrics struct {
|
||||
Utilization Metric
|
||||
Temperature Metric
|
||||
Power Metric
|
||||
PowerLimit Metric
|
||||
MemoryUsed Metric
|
||||
MemoryTotal Metric
|
||||
MemoryUtil Metric
|
||||
FanSpeed Metric
|
||||
Frequency Metric
|
||||
}
|
||||
MemoryActivity Metric
|
||||
EncoderUtil Metric
|
||||
DecoderUtil Metric
|
||||
JPEGUtil Metric
|
||||
OFAUtil Metric
|
||||
MediaUtil Metric
|
||||
ComputeUtil Metric
|
||||
CopyUtil Metric
|
||||
HotspotTemperature Metric
|
||||
FanRPM Metric
|
||||
AICPUUtil Metric
|
||||
CtrlCPUUtil Metric
|
||||
DDRUsed Metric
|
||||
DDRTotal Metric
|
||||
HBMUsed Metric
|
||||
HBMTotal Metric
|
||||
DDRBandwidth Metric
|
||||
HBMBandwidth Metric
|
||||
MemoryBandwidth Metric
|
||||
MediaFrequency Metric
|
||||
HugepagesUsed Metric
|
||||
HugepagesTotal Metric
|
||||
|
||||
type Capabilities struct {
|
||||
Utilization bool
|
||||
Temperature bool
|
||||
Power bool
|
||||
PowerLimit bool
|
||||
Memory bool
|
||||
FanSpeed bool
|
||||
Frequency bool
|
||||
MemoryTemperature Metric
|
||||
Utilization Metric
|
||||
Temperature Metric
|
||||
Power Metric
|
||||
PowerLimit Metric
|
||||
MemoryUsed Metric
|
||||
MemoryTotal Metric
|
||||
MemoryUtil Metric
|
||||
FanSpeed Metric
|
||||
Frequency Metric
|
||||
MemoryFrequency Metric
|
||||
}
|
||||
|
||||
type Process struct {
|
||||
@@ -74,18 +93,19 @@ type Process struct {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ID string
|
||||
Kind Kind
|
||||
Vendor string
|
||||
Index int
|
||||
NPUIndex int
|
||||
ChipIndex int
|
||||
Name string
|
||||
Label string
|
||||
BusID string
|
||||
Metrics Metrics
|
||||
Capabilities Capabilities
|
||||
Processes []Process
|
||||
ParentID string
|
||||
ProcessStatus string
|
||||
ID string
|
||||
Kind Kind
|
||||
Vendor string
|
||||
Index int
|
||||
NPUIndex int
|
||||
ChipIndex int
|
||||
Name string
|
||||
Label string
|
||||
BusID string
|
||||
Metrics Metrics
|
||||
Processes []Process
|
||||
|
||||
GPU *gpu.Device `json:"-"`
|
||||
NPU *npu.Device `json:"-"`
|
||||
@@ -93,10 +113,9 @@ type Device struct {
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
Info Info
|
||||
Devices []Device
|
||||
DriverVersions map[string]string
|
||||
Warnings []error
|
||||
Info Info
|
||||
Devices []Device
|
||||
Warnings []error
|
||||
}
|
||||
|
||||
func (s Snapshot) Warning() error {
|
||||
@@ -104,6 +123,7 @@ func (s Snapshot) Warning() error {
|
||||
}
|
||||
|
||||
type ProviderSnapshot struct {
|
||||
Warnings []string
|
||||
Type string
|
||||
DriverVersion string
|
||||
CudaVersion string
|
||||
|
||||
@@ -3,12 +3,13 @@ package gpu
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -32,16 +33,22 @@ func findAMDSMI() (string, bool) {
|
||||
|
||||
func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
||||
var (
|
||||
staticData string
|
||||
metricData string
|
||||
processData string
|
||||
staticErr error
|
||||
metricErr error
|
||||
processErr error
|
||||
wg sync.WaitGroup
|
||||
staticData string
|
||||
metricData string
|
||||
extendedData string
|
||||
extendedErr error
|
||||
processData string
|
||||
staticErr error
|
||||
metricErr error
|
||||
processErr error
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
wg.Add(3)
|
||||
wg.Add(4)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
extendedData, extendedErr = runAMDSMI(ctx, a.command, "metric", "--clock", "--ecc", "--json")
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
staticData, staticErr = runAMDSMI(ctx, a.command, "static", "--asic", "--bus", "--driver", "--limit", "--json")
|
||||
@@ -64,16 +71,25 @@ func (a amdSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
||||
return nil, fmt.Errorf("parsing %s static output failed: %w", a.command, err)
|
||||
}
|
||||
|
||||
if metricErr == nil {
|
||||
metricErr = applyAMDMetrics(info, metricData)
|
||||
}
|
||||
if metricErr != nil {
|
||||
global.LOG.Warnf("calling %s metric failed, metrics will be omitted: %v", a.command, metricErr)
|
||||
} else if err := applyAMDMetrics(info, metricData); err != nil {
|
||||
global.LOG.Warnf("parsing %s metric output failed, metrics will be omitted: %v", a.command, err)
|
||||
info.Warnings = append(info.Warnings, fmt.Sprintf("%s metrics: %v", a.command, metricErr))
|
||||
}
|
||||
if extendedErr == nil {
|
||||
extendedErr = applyAMDMetrics(info, extendedData)
|
||||
}
|
||||
if extendedErr != nil {
|
||||
info.Warnings = append(info.Warnings, fmt.Sprintf("%s extended metrics: %v", a.command, extendedErr))
|
||||
}
|
||||
if processErr == nil {
|
||||
processErr = applyAMDProcesses(info, processData)
|
||||
}
|
||||
if processErr != nil {
|
||||
global.LOG.Warnf("calling %s process failed, process information will be omitted: %v", a.command, processErr)
|
||||
} else if err := applyAMDProcesses(info, processData); err != nil {
|
||||
global.LOG.Warnf("parsing %s process output failed, process information will be omitted: %v", a.command, err)
|
||||
info.Warnings = append(info.Warnings, fmt.Sprintf("%s processes: %v", a.command, processErr))
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -96,6 +112,9 @@ func parseAMDStatic(data string) (*Info, error) {
|
||||
}
|
||||
device := Device{
|
||||
Type: "amd",
|
||||
UUID: amdStringAt(row, "uuid", "asic.uuid"),
|
||||
DriverVersion: amdStringAt(row, "driver.version", "driver_version", "amdgpu_version"),
|
||||
ProcessStatus: "unavailable",
|
||||
Index: index,
|
||||
ProductName: amdStringAt(row, "asic.market_name", "market_name", "gpu_name"),
|
||||
PersistenceMode: "N/A",
|
||||
@@ -111,6 +130,7 @@ func parseAMDStatic(data string) (*Info, error) {
|
||||
"limit.max_power_limit",
|
||||
"limit.max_power",
|
||||
),
|
||||
PowerLimit: amdMetricAt(row, "W", "limit.ppt0.socket_power_limit", "limit.socket_power_limit"),
|
||||
MemUsed: "N/A",
|
||||
MemTotal: "N/A",
|
||||
GPUUtil: "N/A",
|
||||
@@ -150,11 +170,31 @@ func applyAMDMetrics(info *Info, data string) error {
|
||||
continue
|
||||
}
|
||||
setAMDMetric(&device.GPUUtil, row, "%", "usage.gfx_activity", "usage.gfx", "gfx_activity", "gfx_usage")
|
||||
setAMDMetric(&device.Temperature, row, "°C", "temperature.hotspot", "temperature.edge", "hotspot_temperature", "gpu_temperature", "gpu_temp")
|
||||
setAMDMetric(&device.Temperature, row, "°C", "temperature.edge", "gpu_temperature", "gpu_temp")
|
||||
setAMDMetric(&device.PowerDraw, row, "W", "power.socket_power", "socket_power", "power_usage")
|
||||
setAMDMetric(&device.MemUsed, row, "MB", "mem_usage.used_vram", "vram.used", "used_vram", "vram_used")
|
||||
setAMDMetric(&device.MemTotal, row, "MB", "mem_usage.total_vram", "vram.total", "total_vram", "vram_total")
|
||||
setAMDMetric(&device.FanSpeed, row, "%", "fan.speed", "fan_speed")
|
||||
|
||||
setAMDMetric(&device.FanSpeed, row, "%", "fan.usage")
|
||||
if amdMetricAt(row, "%", "fan.usage") == "" {
|
||||
speed, speedErr := strconv.ParseFloat(amdStringAt(row, "fan.speed"), 64)
|
||||
maximum, maxErr := strconv.ParseFloat(amdStringAt(row, "fan.max"), 64)
|
||||
if speedErr == nil && maxErr == nil && !math.IsNaN(speed) && !math.IsInf(speed, 0) && maximum > 0 && !math.IsInf(maximum, 0) && speed >= 0 && speed <= maximum {
|
||||
device.FanSpeed = fmt.Sprintf("%.2f %%", speed/maximum*100)
|
||||
}
|
||||
}
|
||||
setAMDMetric(&device.FanRPM, row, "RPM", "fan.rpm")
|
||||
setAMDMetric(&device.HotspotTemperature, row, "°C", "temperature.hotspot", "hotspot_temperature")
|
||||
setAMDMetric(&device.MemoryTemperature, row, "°C", "temperature.mem")
|
||||
setAMDMetric(&device.MemoryActivity, row, "%", "usage.umc_activity")
|
||||
setAMDMetric(&device.MediaUtil, row, "%", "usage.mm_activity")
|
||||
setAMDMetric(&device.Frequency, row, "MHz", "clock.gfx_0.clk")
|
||||
setAMDMetric(&device.MemoryFrequency, row, "MHz", "clock.mem_0.clk")
|
||||
correctable := amdStringAt(row, "ecc.total_correctable_count", "ecc.correctable_count")
|
||||
uncorrectable := amdStringAt(row, "ecc.total_uncorrectable_count", "ecc.uncorrectable_count")
|
||||
if correctable != "" || uncorrectable != "" {
|
||||
device.ECCErrors = []ECCError{{Scope: "Total", Correctable: correctable, Uncorrectable: uncorrectable}}
|
||||
}
|
||||
if value := amdStringAt(row, "perf_level", "performance_level"); value != "" {
|
||||
device.PerformanceState = value
|
||||
}
|
||||
@@ -177,6 +217,7 @@ func applyAMDProcesses(info *Info, data string) error {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
device.ProcessStatus = "ok"
|
||||
processList, _ := amdValueAt(row, "process_list")
|
||||
items := amdObjectList(processList)
|
||||
if len(items) == 0 {
|
||||
|
||||
@@ -64,6 +64,7 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
||||
if result.info == nil {
|
||||
continue
|
||||
}
|
||||
merged.Warnings = append(merged.Warnings, result.info.Warnings...)
|
||||
if result.info.Type != "" {
|
||||
types = append(types, result.info.Type)
|
||||
}
|
||||
@@ -86,6 +87,9 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
||||
if len(merged.Devices) == 0 && len(errs) > 0 {
|
||||
return nil, fmt.Errorf("calling GPU monitoring tools failed: %w", errors.Join(errs...))
|
||||
}
|
||||
for _, err := range errs {
|
||||
merged.Warnings = append(merged.Warnings, err.Error())
|
||||
}
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -24,41 +20,10 @@ func (n nvidiaSMI) LoadInfo(ctx context.Context) (*Info, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calling %s failed: %w", nvidiaSMICommand, err)
|
||||
}
|
||||
data := []byte(itemData)
|
||||
version := "v11"
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
decoder := xml.NewDecoder(buf)
|
||||
for {
|
||||
token, err := decoder.Token()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("reading token failed: %w", err)
|
||||
}
|
||||
d, ok := token.(xml.Directive)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
directive := string(d)
|
||||
if !strings.HasPrefix(directive, "DOCTYPE") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(directive, " ")
|
||||
s := strings.Trim(parts[len(parts)-1], "\" ")
|
||||
if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") {
|
||||
version = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd")
|
||||
} else {
|
||||
global.LOG.Debugf("Cannot find schema version in %q", directive)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return parseNvidiaSMI(data, version)
|
||||
return parseNvidiaSMI([]byte(itemData))
|
||||
}
|
||||
|
||||
func parseNvidiaSMI(buf []byte, version string) (*Info, error) {
|
||||
func parseNvidiaSMI(buf []byte) (*Info, error) {
|
||||
var (
|
||||
s nvidiaSMIResponse
|
||||
info Info
|
||||
@@ -72,42 +37,84 @@ func parseNvidiaSMI(buf []byte, version string) (*Info, error) {
|
||||
info.DriverVersion = s.DriverVersion
|
||||
for i := range s.Gpu {
|
||||
gpuItem := Device{
|
||||
Type: "nvidia",
|
||||
Index: uint(i),
|
||||
ProductName: s.Gpu[i].ProductName,
|
||||
PersistenceMode: s.Gpu[i].PersistenceMode,
|
||||
BusID: s.Gpu[i].ID,
|
||||
DisplayActive: s.Gpu[i].DisplayActive,
|
||||
ECC: s.Gpu[i].EccErrors.Volatile.DramUncorrectable,
|
||||
FanSpeed: s.Gpu[i].FanSpeed,
|
||||
Temperature: s.Gpu[i].Temperature.GpuTemp,
|
||||
PerformanceState: s.Gpu[i].PerformanceState,
|
||||
MemUsed: s.Gpu[i].FbMemoryUsage.Used,
|
||||
MemTotal: s.Gpu[i].FbMemoryUsage.Total,
|
||||
GPUUtil: s.Gpu[i].Utilization.GpuUtil,
|
||||
ComputeMode: s.Gpu[i].ComputeMode,
|
||||
MigMode: s.Gpu[i].MigMode.CurrentMig,
|
||||
}
|
||||
if version == "v12" || version == "v13" {
|
||||
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw
|
||||
if gpuItem.PowerDraw == "" {
|
||||
gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.InstantPowerDraw
|
||||
}
|
||||
gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.CurrentPowerLimit
|
||||
} else {
|
||||
gpuItem.PowerDraw = s.Gpu[i].PowerReadings.PowerDraw
|
||||
gpuItem.MaxPowerLimit = s.Gpu[i].PowerReadings.MaxPowerLimit
|
||||
Type: "nvidia",
|
||||
MemoryActivity: s.Gpu[i].Utilization.MemoryUtil,
|
||||
EncoderUtil: s.Gpu[i].Utilization.EncoderUtil,
|
||||
DecoderUtil: s.Gpu[i].Utilization.DecoderUtil,
|
||||
JPEGUtil: s.Gpu[i].Utilization.JpegUtil,
|
||||
OFAUtil: s.Gpu[i].Utilization.OfaUtil,
|
||||
MediaFrequency: s.Gpu[i].Clocks.VideoClock,
|
||||
UUID: s.Gpu[i].UUID,
|
||||
DriverVersion: s.DriverVersion,
|
||||
Architecture: s.Gpu[i].ProductArchitecture,
|
||||
Frequency: s.Gpu[i].Clocks.GraphicsClock,
|
||||
MemoryFrequency: s.Gpu[i].Clocks.MemClock,
|
||||
MemoryTemperature: s.Gpu[i].Temperature.MemoryTemp,
|
||||
MemoryFree: s.Gpu[i].FbMemoryUsage.Free,
|
||||
MemoryReserved: s.Gpu[i].FbMemoryUsage.Reserved,
|
||||
PCIeGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.DeviceCurrentLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.CurrentLinkGen),
|
||||
PCIeMaxGeneration: firstSMIValue(s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxDeviceLinkGen, s.Gpu[i].Pci.PciGpuLinkInfo.PcieGen.MaxLinkGen),
|
||||
PCIeWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.CurrentLinkWidth,
|
||||
PCIeMaxWidth: s.Gpu[i].Pci.PciGpuLinkInfo.LinkWidths.MaxLinkWidth,
|
||||
ProcessStatus: "unavailable",
|
||||
Index: uint(i),
|
||||
ProductName: s.Gpu[i].ProductName,
|
||||
PersistenceMode: s.Gpu[i].PersistenceMode,
|
||||
BusID: s.Gpu[i].ID,
|
||||
DisplayActive: s.Gpu[i].DisplayActive,
|
||||
ECC: s.Gpu[i].EccMode.CurrentEcc,
|
||||
FanSpeed: s.Gpu[i].FanSpeed,
|
||||
Temperature: s.Gpu[i].Temperature.GpuTemp,
|
||||
PerformanceState: s.Gpu[i].PerformanceState,
|
||||
MemUsed: s.Gpu[i].FbMemoryUsage.Used,
|
||||
MemTotal: s.Gpu[i].FbMemoryUsage.Total,
|
||||
GPUUtil: s.Gpu[i].Utilization.GpuUtil,
|
||||
ComputeMode: s.Gpu[i].ComputeMode,
|
||||
MigMode: s.Gpu[i].MigMode.CurrentMig,
|
||||
}
|
||||
gpuItem.ECCPending = s.Gpu[i].EccMode.PendingEcc
|
||||
gpuItem.ECCErrors = []ECCError{
|
||||
{Scope: "Volatile Total", Correctable: s.Gpu[i].EccErrors.Volatile.SingleBit.Total, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DoubleBit.Total},
|
||||
{Scope: "Aggregate Total", Correctable: s.Gpu[i].EccErrors.Aggregate.SingleBit.Total, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.DoubleBit.Total},
|
||||
|
||||
for _, process := range s.Gpu[i].Processes.ProcessInfo {
|
||||
gpuItem.Processes = append(gpuItem.Processes, Process{
|
||||
PID: process.Pid,
|
||||
Type: process.Type,
|
||||
ProcessName: process.ProcessName,
|
||||
UsedMemory: process.UsedMemory,
|
||||
})
|
||||
{Scope: "Volatile DRAM", Correctable: s.Gpu[i].EccErrors.Volatile.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DramUncorrectable},
|
||||
{Scope: "Volatile SRAM", Correctable: s.Gpu[i].EccErrors.Volatile.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Volatile.SramUncorrectable},
|
||||
{Scope: "Aggregate DRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.DramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.DramUncorrectable},
|
||||
{Scope: "Aggregate SRAM", Correctable: s.Gpu[i].EccErrors.Aggregate.SramCorrectable, Uncorrectable: s.Gpu[i].EccErrors.Aggregate.SramUncorrectable},
|
||||
}
|
||||
gpuItem.PowerDraw = firstSMIValue(s.Gpu[i].GpuPowerReadings.PowerDraw, s.Gpu[i].GpuPowerReadings.InstantPowerDraw, s.Gpu[i].PowerReadings.PowerDraw)
|
||||
gpuItem.PowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.CurrentPowerLimit, s.Gpu[i].PowerReadings.EnforcedPowerLimit, s.Gpu[i].PowerReadings.PowerLimit)
|
||||
gpuItem.MaxPowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.MaxPowerLimit, s.Gpu[i].PowerReadings.MaxPowerLimit)
|
||||
gpuItem.DefaultPowerLimit = firstSMIValue(s.Gpu[i].GpuPowerReadings.DefaultPowerLimit, s.Gpu[i].PowerReadings.DefaultPowerLimit)
|
||||
for _, event := range append(s.Gpu[i].ClocksEventReasons.Reasons, s.Gpu[i].ClocksThrottleReasons.Reasons...) {
|
||||
if strings.EqualFold(strings.TrimSpace(event.Value), "Active") {
|
||||
gpuItem.ClockEvents = append(gpuItem.ClockEvents, strings.TrimPrefix(strings.TrimPrefix(event.XMLName.Local, "clocks_event_reason_"), "clocks_throttle_reason_"))
|
||||
}
|
||||
}
|
||||
if s.Gpu[i].Processes != nil && strings.TrimSpace(s.Gpu[i].Processes.Text) == "" {
|
||||
gpuItem.ProcessStatus = "ok"
|
||||
}
|
||||
if s.Gpu[i].Processes != nil {
|
||||
for _, process := range s.Gpu[i].Processes.ProcessInfo {
|
||||
gpuItem.Processes = append(gpuItem.Processes, Process{
|
||||
PID: process.Pid,
|
||||
Type: process.Type,
|
||||
ProcessName: process.ProcessName,
|
||||
UsedMemory: process.UsedMemory,
|
||||
})
|
||||
}
|
||||
}
|
||||
info.Devices = append(info.Devices, gpuItem)
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func firstSMIValue(values ...string) string {
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value != "" && !strings.EqualFold(value, "N/A") && !strings.EqualFold(value, "Not Supported") {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package gpu
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type nvidiaSMIResponse struct {
|
||||
AttachedGpus string `xml:"attached_gpus"`
|
||||
CudaVersion string `xml:"cuda_version"`
|
||||
DriverVersion string `xml:"driver_version"`
|
||||
XMLName xml.Name `xml:"nvidia_smi_log"`
|
||||
AttachedGpus string `xml:"attached_gpus"`
|
||||
CudaVersion string `xml:"cuda_version"`
|
||||
DriverVersion string `xml:"driver_version"`
|
||||
Gpu []struct {
|
||||
ID string `xml:"id,attr"`
|
||||
AccountedProcesses struct{} `xml:"accounted_processes"`
|
||||
@@ -37,16 +40,17 @@ type nvidiaSMIResponse struct {
|
||||
VideoClock string `xml:"video_clock"`
|
||||
} `xml:"clocks"`
|
||||
ClocksEventReasons struct {
|
||||
ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"`
|
||||
ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"`
|
||||
ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"`
|
||||
ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"`
|
||||
ClocksEventReasonHwSlowdown string `xml:"clocks_event_reason_hw_slowdown"`
|
||||
ClocksEventReasonHwThermalSlowdown string `xml:"clocks_event_reason_hw_thermal_slowdown"`
|
||||
ClocksEventReasonSwPowerCap string `xml:"clocks_event_reason_sw_power_cap"`
|
||||
ClocksEventReasonSwThermalSlowdown string `xml:"clocks_event_reason_sw_thermal_slowdown"`
|
||||
ClocksEventReasonSyncBoost string `xml:"clocks_event_reason_sync_boost"`
|
||||
Reasons []struct {
|
||||
XMLName xml.Name
|
||||
Value string `xml:",chardata"`
|
||||
} `xml:",any"`
|
||||
} `xml:"clocks_event_reasons"`
|
||||
ClocksThrottleReasons struct {
|
||||
Reasons []struct {
|
||||
XMLName xml.Name
|
||||
Value string `xml:",chardata"`
|
||||
} `xml:",any"`
|
||||
} `xml:"clocks_throttle_reasons"`
|
||||
ComputeMode string `xml:"compute_mode"`
|
||||
DefaultApplicationsClocks struct {
|
||||
GraphicsClock string `xml:"graphics_clock"`
|
||||
@@ -63,12 +67,24 @@ type nvidiaSMIResponse struct {
|
||||
} `xml:"driver_model"`
|
||||
EccErrors struct {
|
||||
Aggregate struct {
|
||||
SingleBit struct {
|
||||
Total string `xml:"total"`
|
||||
} `xml:"single_bit"`
|
||||
DoubleBit struct {
|
||||
Total string `xml:"total"`
|
||||
} `xml:"double_bit"`
|
||||
DramCorrectable string `xml:"dram_correctable"`
|
||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||
SramCorrectable string `xml:"sram_correctable"`
|
||||
SramUncorrectable string `xml:"sram_uncorrectable"`
|
||||
} `xml:"aggregate"`
|
||||
Volatile struct {
|
||||
SingleBit struct {
|
||||
Total string `xml:"total"`
|
||||
} `xml:"single_bit"`
|
||||
DoubleBit struct {
|
||||
Total string `xml:"total"`
|
||||
} `xml:"double_bit"`
|
||||
DramCorrectable string `xml:"dram_correctable"`
|
||||
DramUncorrectable string `xml:"dram_uncorrectable"`
|
||||
SramCorrectable string `xml:"sram_correctable"`
|
||||
@@ -226,7 +242,8 @@ type nvidiaSMIResponse struct {
|
||||
MinPowerLimit string `xml:"min_power_limit"`
|
||||
MaxPowerLimit string `xml:"max_power_limit"`
|
||||
} `xml:"power_readings"`
|
||||
Processes struct {
|
||||
Processes *struct {
|
||||
Text string `xml:",chardata"`
|
||||
ProcessInfo []struct {
|
||||
Pid string `xml:"pid"`
|
||||
Type string `xml:"type"`
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gpu
|
||||
|
||||
type Info struct {
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
CudaVersion string `json:"cudaVersion"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
Type string `json:"type"`
|
||||
@@ -8,7 +10,42 @@ type Info struct {
|
||||
Devices []Device `json:"gpu"`
|
||||
}
|
||||
|
||||
type ECCError struct {
|
||||
Scope string `json:"scope"`
|
||||
Correctable string `json:"correctable"`
|
||||
Uncorrectable string `json:"uncorrectable"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ECCPending string `json:"eccPending"`
|
||||
ECCErrors []ECCError `json:"eccErrors"`
|
||||
MemoryActivity string `json:"memoryActivity"`
|
||||
EncoderUtil string `json:"encoderUtil"`
|
||||
DecoderUtil string `json:"decoderUtil"`
|
||||
JPEGUtil string `json:"jpegUtil"`
|
||||
OFAUtil string `json:"ofaUtil"`
|
||||
MediaUtil string `json:"mediaUtil"`
|
||||
HotspotTemperature string `json:"hotspotTemperature"`
|
||||
FanRPM string `json:"fanRPM"`
|
||||
MediaFrequency string `json:"mediaFrequency"`
|
||||
|
||||
UUID string `json:"uuid"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
Architecture string `json:"architecture"`
|
||||
Frequency string `json:"frequency"`
|
||||
MemoryFrequency string `json:"memoryFrequency"`
|
||||
MemoryTemperature string `json:"memoryTemperature"`
|
||||
MemoryFree string `json:"memoryFree"`
|
||||
MemoryReserved string `json:"memoryReserved"`
|
||||
PowerLimit string `json:"powerLimit"`
|
||||
DefaultPowerLimit string `json:"defaultPowerLimit"`
|
||||
PCIeGeneration string `json:"pcieGeneration"`
|
||||
PCIeMaxGeneration string `json:"pcieMaxGeneration"`
|
||||
PCIeWidth string `json:"pcieWidth"`
|
||||
PCIeMaxWidth string `json:"pcieMaxWidth"`
|
||||
ProcessStatus string `json:"processStatus"`
|
||||
ClockEvents []string `json:"clockEvents"`
|
||||
|
||||
Type string `json:"type"`
|
||||
Index uint `json:"index"`
|
||||
ProductName string `json:"productName"`
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
@@ -34,7 +35,31 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calling %s failed: %w", ascendSMICommand, err)
|
||||
}
|
||||
return parseAscendSMI(itemData), nil
|
||||
|
||||
info := parseAscendSMI(itemData)
|
||||
var wg sync.WaitGroup
|
||||
warnings := make([]string, len(info.Devices))
|
||||
for i := range info.Devices {
|
||||
wg.Add(1)
|
||||
go func(index int) {
|
||||
defer wg.Done()
|
||||
device := &info.Devices[index]
|
||||
mgr := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second))
|
||||
data, err := mgr.RunWithStdout(ascendSMICommand, "info", "-t", "usages", "-i", strconv.FormatUint(uint64(device.NPUIndex), 10), "-c", strconv.FormatUint(uint64(device.ChipIndex), 10))
|
||||
if err != nil {
|
||||
warnings[index] = fmt.Sprintf("npu-smi usages %d/%d: %v", device.NPUIndex, device.ChipIndex, err)
|
||||
return
|
||||
}
|
||||
applyAscendUsages(device, data)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
for _, warning := range warnings {
|
||||
if warning != "" {
|
||||
info.Warnings = append(info.Warnings, warning)
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func parseAscendSMI(data string) *Info {
|
||||
@@ -158,6 +183,12 @@ func parseAscendSMI(data string) *Info {
|
||||
pending = nil
|
||||
}
|
||||
|
||||
for i := range info.Devices {
|
||||
info.Devices[i].ProcessStatus = "unavailable"
|
||||
if processSection {
|
||||
info.Devices[i].ProcessStatus = "ok"
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
@@ -208,6 +239,9 @@ func ascendMemoryPools(value, header string) (string, string, string, string) {
|
||||
}
|
||||
|
||||
hbm := usage[len(usage)-1]
|
||||
if !strings.Contains(normalizedHeader, "MEMORYUSAGE") {
|
||||
memoryUsed, memoryTotal = "", ""
|
||||
}
|
||||
return memoryUsed, memoryTotal, hbm[0] + " MB", hbm[1] + " MB"
|
||||
}
|
||||
|
||||
@@ -233,3 +267,29 @@ func ascendValueWithUnit(value, unit string) string {
|
||||
}
|
||||
return value + " " + unit
|
||||
}
|
||||
|
||||
func applyAscendUsages(device *Device, data string) {
|
||||
for _, line := range strings.Split(data, "\n") {
|
||||
key, value, ok := strings.Cut(line, ":")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
key = strings.ToLower(strings.Join(strings.Fields(key), ""))
|
||||
value = strings.TrimSpace(value)
|
||||
switch key {
|
||||
case "aicoreusagerate(%)":
|
||||
usage, err := strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(value, "%")), 64)
|
||||
if err == nil && usage >= 0 && usage <= 100 {
|
||||
device.AICore = ascendValueWithUnit(value, "%")
|
||||
}
|
||||
case "aicpuusagerate(%)":
|
||||
device.AICPUUtil = ascendValueWithUnit(value, "%")
|
||||
case "ctrlcpuusagerate(%)":
|
||||
device.CtrlCPUUtil = ascendValueWithUnit(value, "%")
|
||||
case "ddrbandwidthusagerate(%)", "memorybandwidthusagerate(%)":
|
||||
device.DDRBandwidth = ascendValueWithUnit(value, "%")
|
||||
case "hbmbandwidthusagerate(%)":
|
||||
device.HBMBandwidth = ascendValueWithUnit(value, "%")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package npu
|
||||
|
||||
type Info struct {
|
||||
Warnings []string `json:"warnings"`
|
||||
Type string `json:"type"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
Devices []Device `json:"npu"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
AICPUUtil string `json:"aiCPUUtil"`
|
||||
CtrlCPUUtil string `json:"ctrlCPUUtil"`
|
||||
DDRBandwidth string `json:"ddrBandwidth"`
|
||||
HBMBandwidth string `json:"hbmBandwidth"`
|
||||
|
||||
ProcessStatus string `json:"processStatus"`
|
||||
|
||||
Type string `json:"type"`
|
||||
Index uint `json:"index"`
|
||||
NPUIndex uint `json:"npuIndex"`
|
||||
|
||||
@@ -33,16 +33,29 @@ type discoveryInfo struct {
|
||||
}
|
||||
|
||||
type DeviceLevelMetric struct {
|
||||
MetricsType string `json:"metrics_type"`
|
||||
Value float64 `json:"value"`
|
||||
MetricsType string `json:"metrics_type"`
|
||||
Value *float64 `json:"value"`
|
||||
}
|
||||
|
||||
type TileMetrics struct {
|
||||
TileID int `json:"tile_id"`
|
||||
DataList []DeviceLevelMetric `json:"data_list"`
|
||||
}
|
||||
|
||||
type TileStats struct {
|
||||
TileID int `json:"tileID"`
|
||||
Stats Stats `json:"stats"`
|
||||
}
|
||||
|
||||
type DeviceStats struct {
|
||||
TileLevel []TileMetrics `json:"tile_level"`
|
||||
DeviceID int `json:"device_id"`
|
||||
DeviceLevel []DeviceLevelMetric `json:"device_level"`
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Type string `json:"type"`
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
|
||||
@@ -50,12 +63,17 @@ type Info struct {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
Tiles []TileStats `json:"tiles"`
|
||||
ProcessStatus string `json:"processStatus"`
|
||||
|
||||
Basic Basic `json:"basic"`
|
||||
Stats Stats `json:"stats"`
|
||||
Processes []Process `json:"processes"`
|
||||
}
|
||||
|
||||
type Basic struct {
|
||||
UUID string `json:"uuid"`
|
||||
|
||||
DeviceID int `json:"deviceID"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
VendorName string `json:"vendorName"`
|
||||
@@ -66,6 +84,14 @@ type Basic struct {
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
MediaUtil string `json:"mediaUtil"`
|
||||
ComputeUtil string `json:"computeUtil"`
|
||||
CopyUtil string `json:"copyUtil"`
|
||||
MediaFrequency string `json:"mediaFrequency"`
|
||||
MemoryTemperature string `json:"memoryTemperature"`
|
||||
|
||||
MemoryBandwidthUtil string `json:"memoryBandwidthUtil"`
|
||||
|
||||
Power string `json:"power"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
Frequency string `json:"frequency"`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -51,12 +52,15 @@ func (c Client) LoadInfoContext(ctx context.Context) (*Info, error) {
|
||||
|
||||
processData, err := cmdMgr.RunWithStdout(xpuSMICommand, "ps", "-j")
|
||||
if err != nil {
|
||||
global.LOG.Warnf("calling xpu-smi ps failed, process information will be omitted: %v", err)
|
||||
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi ps: %v", err))
|
||||
} else {
|
||||
var psList DeviceUtilByProcList
|
||||
if err := json.Unmarshal([]byte(processData), &psList); err != nil {
|
||||
global.LOG.Warnf("processData json unmarshal failed, process information will be omitted: %v", err)
|
||||
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi ps output: %v", err))
|
||||
} else {
|
||||
for i := range res.Devices {
|
||||
res.Devices[i].ProcessStatus = "ok"
|
||||
}
|
||||
for _, ps := range psList.DeviceUtilByProcList {
|
||||
process := Process{
|
||||
PID: ps.ProcessID,
|
||||
@@ -87,7 +91,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
||||
defer wg.Done()
|
||||
|
||||
xpu := Device{
|
||||
ProcessStatus: "unavailable",
|
||||
Basic: Basic{
|
||||
UUID: device.UUID,
|
||||
DeviceID: device.DeviceID,
|
||||
DeviceName: device.DeviceName,
|
||||
VendorName: device.VendorName,
|
||||
@@ -115,13 +121,19 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
||||
wgCmd.Wait()
|
||||
|
||||
if xpuErr != nil {
|
||||
global.LOG.Errorf("calling xpu-smi discovery failed for device %d, %v", device.DeviceID, xpuErr)
|
||||
mu.Lock()
|
||||
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d: %v", device.DeviceID, xpuErr))
|
||||
res.Devices = append(res.Devices, xpu)
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var info discoveryDevice
|
||||
if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
|
||||
global.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v", device.DeviceID, err)
|
||||
mu.Lock()
|
||||
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d output: %v", device.DeviceID, err))
|
||||
res.Devices = append(res.Devices, xpu)
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,13 +154,22 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
||||
}
|
||||
|
||||
if statsErr != nil {
|
||||
global.LOG.Warnf("calling xpu-smi stats failed for device %d, metrics will be omitted: %v", device.DeviceID, statsErr)
|
||||
mu.Lock()
|
||||
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi stats %d: %v", device.DeviceID, statsErr))
|
||||
mu.Unlock()
|
||||
} else {
|
||||
var stats DeviceStats
|
||||
if err := json.Unmarshal([]byte(statsData), &stats); err != nil {
|
||||
global.LOG.Warnf("statsData json unmarshal failed for device %d, metrics will be omitted: %v", device.DeviceID, err)
|
||||
mu.Lock()
|
||||
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi stats %d output: %v", device.DeviceID, err))
|
||||
mu.Unlock()
|
||||
} else {
|
||||
loadStats(&xpu.Stats, stats.DeviceLevel)
|
||||
for _, tile := range stats.TileLevel {
|
||||
item := TileStats{TileID: tile.TileID}
|
||||
loadStats(&item.Stats, tile.DataList)
|
||||
xpu.Tiles = append(xpu.Tiles, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,19 +183,38 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
|
||||
|
||||
func loadStats(stats *Stats, metrics []DeviceLevelMetric) {
|
||||
for _, stat := range metrics {
|
||||
if stat.Value == nil || math.IsNaN(*stat.Value) || math.IsInf(*stat.Value, 0) {
|
||||
continue
|
||||
}
|
||||
value := *stat.Value
|
||||
if value < 0 && stat.MetricsType != "XPUM_STATS_GPU_CORE_TEMPERATURE" && stat.MetricsType != "XPUM_STATS_MEMORY_TEMPERATURE" {
|
||||
continue
|
||||
}
|
||||
switch stat.MetricsType {
|
||||
case "XPUM_STATS_MEMORY_TEMPERATURE":
|
||||
stats.MemoryTemperature = fmt.Sprintf("%.1f°C", value)
|
||||
case "XPUM_STATS_ENGINE_GROUP_COMPUTE_ALL_UTILIZATION":
|
||||
stats.ComputeUtil = fmt.Sprintf("%.1f%%", value)
|
||||
case "XPUM_STATS_ENGINE_GROUP_MEDIA_ALL_UTILIZATION":
|
||||
stats.MediaUtil = fmt.Sprintf("%.1f%%", value)
|
||||
case "XPUM_STATS_ENGINE_GROUP_COPY_ALL_UTILIZATION":
|
||||
stats.CopyUtil = fmt.Sprintf("%.1f%%", value)
|
||||
case "XPUM_STATS_MEDIA_ENGINE_FREQUENCY":
|
||||
stats.MediaFrequency = fmt.Sprintf("%.1fMHz", value)
|
||||
case "XPUM_STATS_POWER":
|
||||
stats.Power = fmt.Sprintf("%.1fW", stat.Value)
|
||||
stats.Power = fmt.Sprintf("%.1fW", value)
|
||||
case "XPUM_STATS_GPU_UTILIZATION":
|
||||
stats.GPUUtil = fmt.Sprintf("%.1f%%", stat.Value)
|
||||
stats.GPUUtil = fmt.Sprintf("%.1f%%", value)
|
||||
case "XPUM_STATS_GPU_FREQUENCY":
|
||||
stats.Frequency = fmt.Sprintf("%.1fMHz", stat.Value)
|
||||
stats.Frequency = fmt.Sprintf("%.1fMHz", value)
|
||||
case "XPUM_STATS_GPU_CORE_TEMPERATURE":
|
||||
stats.Temperature = fmt.Sprintf("%.1f°C", stat.Value)
|
||||
stats.Temperature = fmt.Sprintf("%.1f°C", value)
|
||||
case "XPUM_STATS_MEMORY_USED":
|
||||
stats.MemoryUsed = fmt.Sprintf("%.1f MiB", stat.Value)
|
||||
case "XPUM_STATS_MEMORY_UTILIZATION", "XPUM_STATS_MEMORY_BANDWIDTH", "XPUM_STATS_MEMORY_BANDWIDTH_UTILIZATION":
|
||||
stats.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value)
|
||||
stats.MemoryUsed = fmt.Sprintf("%.1f MiB", value)
|
||||
case "XPUM_STATS_MEMORY_UTILIZATION":
|
||||
stats.MemoryUtil = fmt.Sprintf("%.1f%%", value)
|
||||
case "XPUM_STATS_MEMORY_BANDWIDTH", "XPUM_STATS_MEMORY_BANDWIDTH_UTILIZATION":
|
||||
stats.MemoryBandwidthUtil = fmt.Sprintf("%.1f%%", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"}
|
||||
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp", "syncIpGroup", "cleanLog"}
|
||||
|
||||
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
params := CreateTaskAlertParams(pushAlert)
|
||||
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
|
||||
alertRule := ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
@@ -76,14 +76,14 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
|
||||
Encryption: emailInfo.Encryption,
|
||||
Recipient: emailInfo.Recipient,
|
||||
}
|
||||
content := GetSendContent(alert.Type, params, agentInfo)
|
||||
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||
}
|
||||
msg := email.EmailMessage{
|
||||
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
|
||||
Body: content,
|
||||
IsHTML: true,
|
||||
IsHTML: GetCronJobType(alert.Type) != "cronJob",
|
||||
}
|
||||
|
||||
if err = email.SendMail(smtpConfig, msg, transport); err != nil {
|
||||
@@ -110,7 +110,7 @@ func CreateBarkAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []
|
||||
return SaveAlertLog(create, &alertLog)
|
||||
}
|
||||
|
||||
content := GetSendContent(alert.Type, params, agentInfo)
|
||||
content := GetAlertLogContent(create, alert, params, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||
}
|
||||
@@ -268,7 +268,7 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
|
||||
alertDetail := dto.AlertDetail{
|
||||
Type: GetCronJobType(alert.Type),
|
||||
SubType: alert.Type,
|
||||
Title: alert.Title,
|
||||
Title: TaskAlertTitle(alert.Type, alert.Title, project, params),
|
||||
Method: method,
|
||||
Project: project,
|
||||
Params: params,
|
||||
@@ -281,6 +281,14 @@ func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param,
|
||||
return string(marshal)
|
||||
}
|
||||
|
||||
func TaskAlertTitle(alertType, title, project string, params []dto.Param) string {
|
||||
if GetCronJobType(alertType) != "cronJob" || CronJobAlertResultFromParams(params) != CronJobAlertSuccess {
|
||||
return title
|
||||
}
|
||||
name := cronJobTaskName(project, params)
|
||||
return i18n.GetMsgWithMap("TaskSuccess", map[string]interface{}{"name": name})
|
||||
}
|
||||
|
||||
func ProcessAlertRule(alert dto.AlertDTO) string {
|
||||
marshal, err := json.Marshal(alert)
|
||||
if err != nil {
|
||||
@@ -324,6 +332,10 @@ func GetCronJobTypeName(cronJobType string) string {
|
||||
module = "系统快照"
|
||||
case "ntp":
|
||||
module = "同步服务器时间"
|
||||
case "syncIpGroup":
|
||||
module = "同步 IP 组"
|
||||
case "cleanLog":
|
||||
module = "清理日志"
|
||||
default:
|
||||
}
|
||||
return module
|
||||
@@ -444,6 +456,30 @@ func isWithinTimeRange(savedTimeString string) bool {
|
||||
}
|
||||
|
||||
func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
|
||||
return GetAlertDetailContent(dto.AlertDetail{Type: alertType, Params: params}, agentInfo)
|
||||
}
|
||||
|
||||
func GetAlertLogContent(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, agentInfo *dto.AgentInfo) string {
|
||||
detail := dto.AlertDetail{Type: alert.Type, Params: params}
|
||||
var stored dto.AlertDetail
|
||||
if json.Unmarshal([]byte(create.AlertDetail), &stored) == nil {
|
||||
detail = stored
|
||||
if detail.Type == "" {
|
||||
detail.Type = alert.Type
|
||||
}
|
||||
if detail.Params == nil {
|
||||
detail.Params = params
|
||||
}
|
||||
}
|
||||
return GetAlertDetailContent(detail, agentInfo)
|
||||
}
|
||||
|
||||
func GetAlertDetailContent(detail dto.AlertDetail, agentInfo *dto.AgentInfo) string {
|
||||
alertType := detail.SubType
|
||||
if alertType == "" {
|
||||
alertType = detail.Type
|
||||
}
|
||||
params := detail.Params
|
||||
switch GetCronJobType(alertType) {
|
||||
case "ssl":
|
||||
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
@@ -464,7 +500,12 @@ func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentIn
|
||||
case "disk":
|
||||
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
case "cronJob":
|
||||
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
messageKey := "CronJobFailedAlert"
|
||||
if CronJobAlertResultFromParams(params) == CronJobAlertSuccess {
|
||||
messageKey = "CronJobSuccessAlert"
|
||||
}
|
||||
name := cronJobTaskName(detail.Project, params)
|
||||
return i18n.GetMsgWithMap(messageKey, map[string]interface{}{"name": name, "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
case "clams":
|
||||
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
|
||||
case "panelLogin":
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package alert
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
)
|
||||
|
||||
const (
|
||||
CronJobAlertFailed = "failed"
|
||||
CronJobAlertSuccess = "success"
|
||||
CronJobAlertBoth = "both"
|
||||
)
|
||||
|
||||
func cronJobAlertParams(advanced string) (map[string]json.RawMessage, error) {
|
||||
params := make(map[string]json.RawMessage)
|
||||
if strings.TrimSpace(advanced) != "" {
|
||||
if err := json.Unmarshal([]byte(advanced), ¶ms); err != nil {
|
||||
return nil, fmt.Errorf("invalid cronjob alert advanced parameters: %w", err)
|
||||
}
|
||||
}
|
||||
if params == nil {
|
||||
params = make(map[string]json.RawMessage)
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func CronJobAlertTriggerMode(advanced string) (string, error) {
|
||||
params, err := cronJobAlertParams(advanced)
|
||||
if err != nil {
|
||||
return CronJobAlertFailed, err
|
||||
}
|
||||
mode := CronJobAlertFailed
|
||||
if raw, ok := params["alertTriggerMode"]; ok {
|
||||
if err := json.Unmarshal(raw, &mode); err != nil {
|
||||
return CronJobAlertFailed, fmt.Errorf("invalid cronjob alert trigger mode: %w", err)
|
||||
}
|
||||
}
|
||||
switch mode {
|
||||
case CronJobAlertFailed, CronJobAlertSuccess, CronJobAlertBoth:
|
||||
return mode, nil
|
||||
default:
|
||||
return CronJobAlertFailed, fmt.Errorf("invalid cronjob alert trigger mode %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func MergeCronJobAlertParams(previous, incoming string) (string, error) {
|
||||
params, err := cronJobAlertParams(previous)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
updates, err := cronJobAlertParams(incoming)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for key, value := range updates {
|
||||
params[key] = value
|
||||
}
|
||||
if _, ok := params["alertTriggerMode"]; !ok {
|
||||
params["alertTriggerMode"] = json.RawMessage(`"failed"`)
|
||||
}
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := CronJobAlertTriggerMode(string(data)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func MatchCronJobAlertResult(advanced, result string) bool {
|
||||
mode, err := CronJobAlertTriggerMode(advanced)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if result == "" {
|
||||
result = CronJobAlertFailed
|
||||
}
|
||||
if result != CronJobAlertFailed && result != CronJobAlertSuccess {
|
||||
return false
|
||||
}
|
||||
return mode == CronJobAlertBoth || mode == result
|
||||
}
|
||||
|
||||
func CreateTaskAlertParams(pushAlert dto.PushAlert) []dto.Param {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
if GetCronJobType(pushAlert.AlertType) != "cronJob" {
|
||||
return params
|
||||
}
|
||||
params = append(params, CreateCronJobResultParam(pushAlert.Result))
|
||||
return params
|
||||
}
|
||||
|
||||
func cronJobTaskName(project string, params []dto.Param) string {
|
||||
if project != "" {
|
||||
return project
|
||||
}
|
||||
if name := getValueByIndex(params, "taskName"); name != "" {
|
||||
return name
|
||||
}
|
||||
return getValueByIndex(params, "1")
|
||||
}
|
||||
|
||||
func CreateCronJobResultParam(result string) dto.Param {
|
||||
value := "失败"
|
||||
if result == CronJobAlertSuccess {
|
||||
value = "成功"
|
||||
}
|
||||
return dto.Param{Index: "2", Key: "result", Value: value}
|
||||
}
|
||||
|
||||
func CronJobAlertResultFromParams(params []dto.Param) string {
|
||||
for _, param := range params {
|
||||
if param.Index == "result" {
|
||||
if param.Value == CronJobAlertSuccess {
|
||||
return CronJobAlertSuccess
|
||||
}
|
||||
return CronJobAlertFailed
|
||||
}
|
||||
}
|
||||
for _, param := range params {
|
||||
if param.Index == "2" && param.Key == "result" && param.Value == "成功" {
|
||||
return CronJobAlertSuccess
|
||||
}
|
||||
}
|
||||
return CronJobAlertFailed
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func CreateTaskScanCustomWebhookAlertLog(
|
||||
transport *http.Transport,
|
||||
agentInfo *dto.AgentInfo,
|
||||
) error {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
params := CreateTaskAlertParams(pushAlert)
|
||||
alertInfo := info
|
||||
alertInfo.Type = alertType
|
||||
create.Type = GetCronJobType(alertType)
|
||||
@@ -99,7 +99,7 @@ func customWebhookTemplateData(rawDetail string, agentInfo *dto.AgentInfo, occur
|
||||
if businessType == "" {
|
||||
return webhook_sender.TemplateData{}, errors.New("resolve custom webhook alert detail failed")
|
||||
}
|
||||
content := GetSendContent(businessType, detail.Params, agentInfo)
|
||||
content := GetAlertDetailContent(detail, agentInfo)
|
||||
if content == "" {
|
||||
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": detail.Title})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ func PushAlert(pushAlert dto.PushAlert) error {
|
||||
}
|
||||
var alert dto.AlertDTO
|
||||
_ = copier.Copy(&alert, &alertInfo)
|
||||
if alertUtil.GetCronJobType(pushAlert.AlertType) == "cronJob" && !alertUtil.MatchCronJobAlertResult(alert.AdvancedParams, pushAlert.Result) {
|
||||
return nil
|
||||
}
|
||||
|
||||
methods := strings.Split(alert.Method, ",")
|
||||
for _, m := range methods {
|
||||
@@ -126,7 +129,7 @@ func sendAlert(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert dto.Push
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param))
|
||||
params := alertUtil.CreateTaskAlertParams(pushAlert)
|
||||
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
|
||||
alertRule := alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
)
|
||||
|
||||
type NetworkCleanupClient interface {
|
||||
NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error)
|
||||
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
|
||||
NetworkInspect(context.Context, string, network.InspectOptions) (network.Inspect, error)
|
||||
NetworkRemove(context.Context, string) error
|
||||
}
|
||||
|
||||
func CleanUnusedNetworks(ctx context.Context, cli NetworkCleanupClient, onResult ...func(string, dto.NetworkCleanupItem)) (*dto.NetworkCleanupReport, error) {
|
||||
networks, err := cli.NetworkList(ctx, network.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
report := &dto.NetworkCleanupReport{Deleted: []dto.NetworkCleanupItem{}, Skipped: []dto.NetworkCleanupItem{}, Failed: []dto.NetworkCleanupItem{}}
|
||||
record := func(status string, item dto.NetworkCleanupItem) {
|
||||
switch status {
|
||||
case "deleted":
|
||||
report.Deleted = append(report.Deleted, item)
|
||||
case "skipped":
|
||||
report.Skipped = append(report.Skipped, item)
|
||||
case "failed":
|
||||
report.Failed = append(report.Failed, item)
|
||||
}
|
||||
for _, notify := range onResult {
|
||||
if notify != nil {
|
||||
notify(status, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 report, err
|
||||
}
|
||||
item := dto.NetworkCleanupItem{ID: n.ID, Name: n.Name}
|
||||
switch {
|
||||
case n.Name == "none" || n.Name == "host" || n.Name == "bridge" || n.Name == "1panel-network":
|
||||
item.Reason = "protected"
|
||||
case n.Scope != "local" || n.Ingress || n.ConfigOnly:
|
||||
item.Reason = "unsupported_network"
|
||||
case used[n.Name] || used[n.ID]:
|
||||
item.Reason = "container_connected"
|
||||
}
|
||||
if item.Reason != "" {
|
||||
record("skipped", item)
|
||||
continue
|
||||
}
|
||||
inspected, err := cli.NetworkInspect(ctx, n.ID, network.InspectOptions{})
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
item.Reason = "already_removed"
|
||||
record("skipped", item)
|
||||
} else {
|
||||
item.Reason = "inspect_failed"
|
||||
record("failed", item)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(inspected.Containers) > 0 {
|
||||
item.Reason = "container_connected"
|
||||
record("skipped", item)
|
||||
continue
|
||||
}
|
||||
if err := cli.NetworkRemove(ctx, n.ID); err != nil {
|
||||
switch {
|
||||
case errdefs.IsNotFound(err):
|
||||
item.Reason = "already_removed"
|
||||
record("skipped", item)
|
||||
case errdefs.IsConflict(err):
|
||||
item.Reason = "network_in_use"
|
||||
record("skipped", item)
|
||||
default:
|
||||
item.Reason = "remove_failed"
|
||||
record("failed", item)
|
||||
}
|
||||
continue
|
||||
}
|
||||
record("deleted", item)
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
)
|
||||
|
||||
type cleanupFake struct {
|
||||
networks []network.Inspect
|
||||
containers []container.Summary
|
||||
inspected map[string]network.Inspect
|
||||
inspectErrors, removeErrors map[string]error
|
||||
listError error
|
||||
removed []string
|
||||
}
|
||||
|
||||
func (f *cleanupFake) NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error) {
|
||||
return f.networks, nil
|
||||
}
|
||||
func (f *cleanupFake) ContainerList(_ context.Context, o container.ListOptions) ([]container.Summary, error) {
|
||||
if !o.All {
|
||||
panic("must include stopped containers")
|
||||
}
|
||||
return f.containers, f.listError
|
||||
}
|
||||
func (f *cleanupFake) NetworkInspect(_ context.Context, id string, _ network.InspectOptions) (network.Inspect, error) {
|
||||
return f.inspected[id], f.inspectErrors[id]
|
||||
}
|
||||
func (f *cleanupFake) NetworkRemove(_ context.Context, id string) error {
|
||||
f.removed = append(f.removed, id)
|
||||
return f.removeErrors[id]
|
||||
}
|
||||
|
||||
func TestNetworkCleanupProtectsAndReports(t *testing.T) {
|
||||
f := &cleanupFake{inspected: map[string]network.Inspect{"attached": {Containers: map[string]network.EndpointResource{"container": {}}}}, inspectErrors: map[string]error{"unknown": errors.New("inspect failure")}, removeErrors: map[string]error{"race": errdefs.Conflict(errors.New("has active endpoints")), "gone": errdefs.NotFound(errors.New("gone")), "failed": errors.New("denied")}}
|
||||
for _, name := range []string{"none", "host", "bridge", "1panel-network", "configured", "attached", "stopped", "free", "unknown", "race", "gone", "failed"} {
|
||||
f.networks = append(f.networks, network.Inspect{ID: name, Name: name, Scope: "local"})
|
||||
}
|
||||
// Compose ownership labels do not protect an otherwise unused network.
|
||||
for i := range f.networks {
|
||||
if f.networks[i].Name == "configured" {
|
||||
f.networks[i].Labels = map[string]string{"com.docker.compose.project": "demo", "com.docker.compose.network": "default"}
|
||||
}
|
||||
}
|
||||
f.containers = []container.Summary{{State: "exited", NetworkSettings: &container.NetworkSettingsSummary{Networks: map[string]*network.EndpointSettings{"stopped": {NetworkID: "stopped"}}}}}
|
||||
report, err := CleanUnusedNetworks(context.Background(), f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(f.removed, []string{"configured", "failed", "free", "gone", "race"}) {
|
||||
t.Fatal(f.removed)
|
||||
}
|
||||
if len(report.Deleted) != 2 || report.Deleted[0].Name != "configured" || report.Deleted[1].Name != "free" || len(report.Failed) != 2 || len(report.Skipped) != 8 {
|
||||
t.Fatalf("%+v", report)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkCleanupDiscoveryFailureDeletesNothing(t *testing.T) {
|
||||
f := &cleanupFake{networks: []network.Inspect{{ID: "free", Name: "free", Scope: "local"}}, listError: errors.New("cannot list containers")}
|
||||
if _, err := CleanUnusedNetworks(context.Background(), f); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if len(f.removed) > 0 {
|
||||
t.Fatal(f.removed)
|
||||
}
|
||||
}
|
||||
@@ -15010,6 +15010,30 @@ const docTemplate = `{
|
||||
]
|
||||
}
|
||||
},
|
||||
"/databases/redis/cli/status": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Load redis-cli installation status",
|
||||
"tags": [
|
||||
"Database Redis"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/databases/redis/conf": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -15092,9 +15116,26 @@ const docTemplate = `{
|
||||
},
|
||||
"/databases/redis/install/cli": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -39731,6 +39772,31 @@ const docTemplate = `{
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"dto.RedisCliInstall": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisCliStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorMsg": {
|
||||
"type": "string"
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"properties": {
|
||||
"containerName": {
|
||||
|
||||
@@ -15006,6 +15006,30 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/databases/redis/cli/status": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Load redis-cli installation status",
|
||||
"tags": [
|
||||
"Database Redis"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/databases/redis/conf": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -15088,9 +15112,26 @@
|
||||
},
|
||||
"/databases/redis/install/cli": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -39727,6 +39768,31 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"dto.RedisCliInstall": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisCliStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorMsg": {
|
||||
"type": "string"
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"properties": {
|
||||
"containerName": {
|
||||
|
||||
@@ -644,6 +644,13 @@
|
||||
"formatZH": "创建容器网络 name",
|
||||
"formatEN": "create container network [name]"
|
||||
},
|
||||
"/containers/network/clean": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "清理未使用的容器网络",
|
||||
"formatEN": "Clean unused container networks"
|
||||
},
|
||||
"/containers/network/del": {
|
||||
"bodyKeys": [
|
||||
"names"
|
||||
@@ -4028,12 +4035,21 @@
|
||||
},
|
||||
"/runtimes/operate": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
"ID"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "操作运行环境 [id]",
|
||||
"formatEN": "Operate runtime [id]"
|
||||
"beforeFunctions": [
|
||||
{
|
||||
"input_column": "id",
|
||||
"input_value": "ID",
|
||||
"isList": false,
|
||||
"db": "runtimes",
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"formatZH": "操作运行环境 [name]",
|
||||
"formatEN": "Operate runtime [name]"
|
||||
},
|
||||
"/runtimes/php/config": {
|
||||
"bodyKeys": [
|
||||
|
||||
@@ -27,6 +27,11 @@ var demoAllowedRoutes = map[demoRoute]struct{}{
|
||||
{http.MethodPost, "/api/v2/toolbox/clam/base"}: {},
|
||||
{http.MethodPost, "/api/v2/backups/record/size"}: {},
|
||||
|
||||
{http.MethodPost, "/api/v2/hosts/firewall/base"}: {},
|
||||
{http.MethodPost, "/api/v2/hosts/firewall/forward/base"}: {},
|
||||
{http.MethodPost, "/api/v2/hosts/firewall/rules/native/detail"}: {},
|
||||
{http.MethodPost, "/api/v2/hosts/firewall/rules/sync/preview"}: {},
|
||||
|
||||
{http.MethodPost, "/api/v2/core/auth/login"}: {},
|
||||
{http.MethodPost, "/api/v2/core/logs/login"}: {},
|
||||
{http.MethodPost, "/api/v2/core/logs/operation"}: {},
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# 批量清理未使用的容器网络
|
||||
|
||||
`POST /api/v2/containers/network/clean`,使用现有 1Panel 认证,不需要请求体。
|
||||
这是异步接口,立即返回 `data.taskID`。实际结果在任务中心和任务日志查看,HTTP 请求结束不会取消任务。
|
||||
|
||||
规则:
|
||||
|
||||
- 永远保留 `none`、`host`、`bridge`、`1panel-network`。
|
||||
- 保留所有容器(包括停止容器)引用的网络,删除前再通过 NetworkInspect 检查连接。
|
||||
- 不检查 Compose 文件或应用配置引用;仅被这些配置引用、没有容器连接的普通网络也会被删除。
|
||||
- 当前仅清理本地普通网络,跳过 Swarm、ingress 和 config-only 网络。
|
||||
- Docker 删除时发现新连接会拒绝删除;不会强制断开容器。
|
||||
|
||||
成功响应示例:
|
||||
|
||||
```json
|
||||
{"code":200,"data":{"taskID":"服务端生成的任务 UUID"}}
|
||||
```
|
||||
|
||||
任务类型为 Container,操作为 TaskClean。网络页“清理网络”按钮调用此接口,拿到 taskID 后立即打开任务日志,并刷新任务中心。
|
||||
每个网络处理完成后写入名称、ID 和结果;有容器连接或 Docker 返回占用冲突时,明确记录“未删除”。最后记录已删除、跳过和失败数量。
|
||||
跳过在用网络属于正常结果;检查/删除失败会将任务标记为失败,已删除的网络不回滚。
|
||||
|
||||
日志调用链:
|
||||
|
||||
- `agent/app/service/container_network_cleanup.go`:NewTaskWithOps 创建任务,AddSubTask 注册清理步骤,Execute 执行任务,t.Log 写逐项日志。
|
||||
- `agent/app/task/task.go`:任务持久化,维护状态,写入开始、结束标记;日志文件为 `<global.Dir.TaskDir>/Container/<taskID>.log`。
|
||||
- `frontend/src/views/container/network/index.vue`:请求成功后调用 openTaskLog(taskID)。
|
||||
- `frontend/src/components/log/task/index.vue`:openWithTaskID 打开日志弹窗。
|
||||
- `frontend/src/components/log/file/index.vue`:通过 `/logs/tasks/read` 按行读取任务日志。
|
||||
|
||||
范围:不检查 Compose 文件或应用配置引用;仅清理本地普通网络。原 `/containers/prune` 接口仍保持原逻辑。
|
||||
测试使用假 Docker 客户端和临时任务数据库,不执行真实网络删除。
|
||||
Generated
+10
-173
@@ -126,7 +126,6 @@
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
@@ -1014,7 +1013,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1031,7 +1029,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1048,7 +1045,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1065,7 +1061,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1082,7 +1077,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1099,7 +1093,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1116,7 +1109,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1133,7 +1125,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1150,7 +1141,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1167,7 +1157,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1184,7 +1173,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1201,7 +1189,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1218,7 +1205,6 @@
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1235,7 +1221,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1252,7 +1237,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1269,7 +1253,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1286,7 +1269,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1303,7 +1285,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1320,7 +1301,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1337,7 +1317,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1354,7 +1333,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1371,7 +1349,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1388,7 +1365,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1405,7 +1381,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1422,7 +1397,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1439,7 +1413,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1773,18 +1746,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
@@ -2013,7 +1974,6 @@
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -2053,7 +2013,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2074,7 +2033,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2095,7 +2053,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2116,7 +2073,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2137,7 +2093,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2158,7 +2113,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2179,7 +2133,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2200,7 +2153,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2221,7 +2173,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2242,7 +2193,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2263,7 +2213,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2284,7 +2233,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2305,7 +2253,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2350,7 +2297,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2367,7 +2313,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2384,7 +2329,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2401,7 +2345,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2418,7 +2361,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2435,7 +2377,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2452,7 +2393,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2469,7 +2409,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2486,7 +2425,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2503,7 +2441,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2520,7 +2457,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2537,7 +2473,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2554,7 +2489,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2571,7 +2505,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2984,7 +2917,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
@@ -3016,9 +2948,8 @@
|
||||
"version": "26.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz",
|
||||
"integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
@@ -3321,7 +3252,6 @@
|
||||
"integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.68.0",
|
||||
"@typescript-eslint/types": "8.68.0",
|
||||
@@ -4005,7 +3935,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz",
|
||||
"integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "14.4.0",
|
||||
@@ -4052,15 +3981,13 @@
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4317,7 +4244,6 @@
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -4347,7 +4273,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.11.12",
|
||||
"caniuse-lite": "^1.0.30001809",
|
||||
@@ -4688,7 +4613,6 @@
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
@@ -5373,7 +5297,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
@@ -5766,7 +5689,6 @@
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
@@ -5828,7 +5750,6 @@
|
||||
"integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -5888,7 +5809,6 @@
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -6240,7 +6160,6 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -6342,7 +6261,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -6822,7 +6740,7 @@
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz",
|
||||
"integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/imurmurhash": {
|
||||
@@ -6895,7 +6813,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -6921,7 +6839,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
@@ -6966,7 +6884,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
@@ -7052,7 +6969,7 @@
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
@@ -7743,15 +7660,13 @@
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
|
||||
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-unified": {
|
||||
"version": "1.0.3",
|
||||
@@ -7955,7 +7870,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.5.0",
|
||||
@@ -8253,7 +8167,6 @@
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -8441,7 +8354,6 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
@@ -8697,7 +8609,6 @@
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
@@ -8722,7 +8633,6 @@
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
|
||||
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^7.7.7"
|
||||
},
|
||||
@@ -8803,7 +8713,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.17",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -8915,7 +8824,6 @@
|
||||
"integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -9284,7 +9192,6 @@
|
||||
"integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.143.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
@@ -9394,9 +9301,8 @@
|
||||
"version": "1.103.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.103.1.tgz",
|
||||
"integrity": "sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^5.0.0",
|
||||
"immutable": "^5.1.5",
|
||||
@@ -9622,29 +9528,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-correct": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
||||
@@ -9989,34 +9872,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.44.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
|
||||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/text-extensions": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
|
||||
@@ -10098,7 +9953,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -10110,7 +9964,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -10175,7 +10028,6 @@
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -10214,7 +10066,7 @@
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unimport": {
|
||||
@@ -10604,7 +10456,6 @@
|
||||
"integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.33.0",
|
||||
"picomatch": "^4.0.5",
|
||||
@@ -10879,7 +10730,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10900,7 +10750,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10921,7 +10770,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10942,7 +10790,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10963,7 +10810,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10984,7 +10830,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11005,7 +10850,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11026,7 +10870,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11047,7 +10890,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11068,7 +10910,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11089,7 +10930,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -11128,7 +10968,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz",
|
||||
"integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.42",
|
||||
"@vue/compiler-sfc": "3.5.42",
|
||||
@@ -11182,7 +11021,6 @@
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
@@ -11209,7 +11047,6 @@
|
||||
"integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"eslint-scope": "^8.2.0 || ^9.0.0",
|
||||
|
||||
@@ -22,6 +22,8 @@ export namespace AI {
|
||||
}
|
||||
|
||||
export interface Info {
|
||||
collectedAt?: string;
|
||||
warnings?: string[];
|
||||
cudaVersion: string;
|
||||
driverVersion: string;
|
||||
type: string;
|
||||
@@ -31,6 +33,34 @@ export namespace AI {
|
||||
xpu: XpuInfo['xpu'];
|
||||
}
|
||||
export interface GPU {
|
||||
eccPending?: string;
|
||||
eccErrors?: { scope: string; correctable: string; uncorrectable: string }[];
|
||||
memoryActivity?: string;
|
||||
encoderUtil?: string;
|
||||
decoderUtil?: string;
|
||||
jpegUtil?: string;
|
||||
ofaUtil?: string;
|
||||
mediaUtil?: string;
|
||||
hotspotTemperature?: string;
|
||||
fanRPM?: string;
|
||||
mediaFrequency?: string;
|
||||
|
||||
uuid?: string;
|
||||
driverVersion?: string;
|
||||
architecture?: string;
|
||||
frequency?: string;
|
||||
memoryFrequency?: string;
|
||||
memoryTemperature?: string;
|
||||
memoryFree?: string;
|
||||
memoryReserved?: string;
|
||||
powerLimit?: string;
|
||||
defaultPowerLimit?: string;
|
||||
pcieGeneration?: string;
|
||||
pcieMaxGeneration?: string;
|
||||
pcieWidth?: string;
|
||||
pcieMaxWidth?: string;
|
||||
clockEvents?: string[];
|
||||
processStatus?: string;
|
||||
type: 'nvidia' | 'amd';
|
||||
index: number;
|
||||
productName: string;
|
||||
@@ -52,6 +82,12 @@ export namespace AI {
|
||||
processes: Process[];
|
||||
}
|
||||
export interface NPU {
|
||||
aiCPUUtil?: string;
|
||||
ctrlCPUUtil?: string;
|
||||
ddrBandwidth?: string;
|
||||
hbmBandwidth?: string;
|
||||
|
||||
processStatus?: string;
|
||||
type: 'ascend';
|
||||
index: number;
|
||||
npuIndex: number;
|
||||
@@ -73,6 +109,9 @@ export namespace AI {
|
||||
processes: NPUProcess[];
|
||||
}
|
||||
export interface MonitorGPUSearch {
|
||||
aggregation?: 'avg' | 'max';
|
||||
deviceID?: string;
|
||||
legacy?: boolean;
|
||||
productName: string;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
@@ -83,8 +122,10 @@ export namespace AI {
|
||||
chartHide: Array<ChartHide>;
|
||||
}
|
||||
export interface ChartHide {
|
||||
deviceID?: string;
|
||||
legacy?: boolean;
|
||||
productName: string;
|
||||
type?: 'gpu' | 'xpu';
|
||||
type?: 'gpu' | 'npu' | 'xpu';
|
||||
process: boolean;
|
||||
gpu: boolean;
|
||||
memory: boolean;
|
||||
@@ -94,17 +135,45 @@ export namespace AI {
|
||||
speed: boolean;
|
||||
}
|
||||
export interface MonitorGPUData {
|
||||
memoryActivity: Array<number | null>;
|
||||
encoderUtil: Array<number | null>;
|
||||
decoderUtil: Array<number | null>;
|
||||
jpegUtil: Array<number | null>;
|
||||
ofaUtil: Array<number | null>;
|
||||
mediaUtil: Array<number | null>;
|
||||
computeUtil: Array<number | null>;
|
||||
copyUtil: Array<number | null>;
|
||||
hotspotTemperature: Array<number | null>;
|
||||
fanRPM: Array<number | null>;
|
||||
aiCPUUtil: Array<number | null>;
|
||||
ctrlCPUUtil: Array<number | null>;
|
||||
ddrUsed: Array<number | null>;
|
||||
ddrTotal: Array<number | null>;
|
||||
hbmUsed: Array<number | null>;
|
||||
hbmTotal: Array<number | null>;
|
||||
ddrBandwidth: Array<number | null>;
|
||||
hbmBandwidth: Array<number | null>;
|
||||
memoryBandwidth: Array<number | null>;
|
||||
mediaFrequency: Array<number | null>;
|
||||
hugepagesUsed: Array<number | null>;
|
||||
hugepagesTotal: Array<number | null>;
|
||||
|
||||
bucketSeconds: number;
|
||||
sampleCount: number;
|
||||
memoryTemperatureValue: Array<number | null>;
|
||||
frequencyValue: Array<number | null>;
|
||||
memoryFrequencyValue: Array<number | null>;
|
||||
date: Array<Date>;
|
||||
gpuValue: Array<number>;
|
||||
temperatureValue: Array<number>;
|
||||
powerTotal: Array<number>;
|
||||
powerUsed: Array<number>;
|
||||
powerPercent: Array<number>;
|
||||
memoryTotal: Array<number>;
|
||||
memoryUsed: Array<number>;
|
||||
memoryPercent: Array<number>;
|
||||
speedValue: Array<number>;
|
||||
processCount: Array<number>;
|
||||
gpuValue: Array<number | null>;
|
||||
temperatureValue: Array<number | null>;
|
||||
powerTotal: Array<number | null>;
|
||||
powerUsed: Array<number | null>;
|
||||
powerPercent: Array<number | null>;
|
||||
memoryTotal: Array<number | null>;
|
||||
memoryUsed: Array<number | null>;
|
||||
memoryPercent: Array<number | null>;
|
||||
speedValue: Array<number | null>;
|
||||
processCount: Array<number | null>;
|
||||
gpuProcesses: Array<Array<GPUProcess>>;
|
||||
}
|
||||
export interface GPUProcess {
|
||||
@@ -132,12 +201,15 @@ export namespace AI {
|
||||
}
|
||||
|
||||
interface Xpu {
|
||||
tiles?: { tileID: number; stats: Stats }[];
|
||||
processStatus?: string;
|
||||
basic: Basic;
|
||||
stats: Stats;
|
||||
processes: XpuProcess[];
|
||||
}
|
||||
|
||||
interface Basic {
|
||||
uuid?: string;
|
||||
deviceID: number;
|
||||
deviceName: string;
|
||||
vendorName: string;
|
||||
@@ -148,6 +220,13 @@ export namespace AI {
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
mediaUtil?: string;
|
||||
computeUtil?: string;
|
||||
copyUtil?: string;
|
||||
mediaFrequency?: string;
|
||||
memoryTemperature?: string;
|
||||
|
||||
memoryBandwidthUtil?: string;
|
||||
power: string;
|
||||
gpuUtil: string;
|
||||
frequency: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CommonModel, ReqPage } from '@/api/interface';
|
||||
import type { CronjobAlertTriggerMode } from '@/utils/cronjob-alert';
|
||||
|
||||
export namespace Alert {
|
||||
export interface AlertInfo extends CommonModel {
|
||||
@@ -9,22 +10,25 @@ export namespace Alert {
|
||||
interval: number;
|
||||
method: string;
|
||||
title: string;
|
||||
taskName?: string;
|
||||
project: string;
|
||||
status: string;
|
||||
sendCount: number;
|
||||
sendMethod: string[];
|
||||
advancedParams: string;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
createUser?: string;
|
||||
updateUser?: string;
|
||||
}
|
||||
|
||||
export interface AlertDetail {
|
||||
type: string;
|
||||
subType?: string;
|
||||
licenseId: string;
|
||||
title: string;
|
||||
project: string;
|
||||
method: string;
|
||||
params: string;
|
||||
params: { index: string; key: string; value: string }[] | string;
|
||||
}
|
||||
|
||||
export interface AlertUpdateStatusReq {
|
||||
@@ -74,6 +78,7 @@ export namespace Alert {
|
||||
project: string;
|
||||
status: string;
|
||||
sendCount: number;
|
||||
advancedParams?: string;
|
||||
}
|
||||
|
||||
export interface AlertUpdateReq extends CommonModel {
|
||||
@@ -86,6 +91,7 @@ export namespace Alert {
|
||||
project: string;
|
||||
status: string;
|
||||
sendCount: number;
|
||||
advancedParams?: string;
|
||||
}
|
||||
|
||||
export interface DelReq {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReqPage } from '.';
|
||||
import type { CronjobAlertTriggerMode } from '@/utils/cronjob-alert';
|
||||
|
||||
export namespace Cronjob {
|
||||
export interface Search extends ReqPage {
|
||||
@@ -62,6 +63,7 @@ export namespace Cronjob {
|
||||
secret: string;
|
||||
hasAlert: boolean;
|
||||
alertCount: number;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
alertTitle: string;
|
||||
alertMethod: string;
|
||||
alertMethodItems: Array<string>;
|
||||
@@ -111,6 +113,7 @@ export namespace Cronjob {
|
||||
secret: string;
|
||||
|
||||
alertCount: number;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
alertTitle: string;
|
||||
alertMethod: string;
|
||||
|
||||
@@ -154,6 +157,7 @@ export namespace Cronjob {
|
||||
downloadAccount: string;
|
||||
|
||||
alertCount: number;
|
||||
alertTriggerMode?: CronjobAlertTriggerMode;
|
||||
}
|
||||
export interface TransHelper {
|
||||
name: string;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { ReqPage } from '.';
|
||||
|
||||
export namespace Database {
|
||||
export interface RedisCliStatus {
|
||||
installed: boolean;
|
||||
taskID: string;
|
||||
status: string;
|
||||
errorMsg: string;
|
||||
}
|
||||
|
||||
export interface SearchDBWithPage {
|
||||
info: string;
|
||||
database: string;
|
||||
|
||||
@@ -3,13 +3,12 @@ import { ResPage, SearchWithPage } from '../interface';
|
||||
import { Container } from '../interface/container';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
|
||||
export const searchContainer = (params: Container.ContainerSearch, currentNode?: string) => {
|
||||
return http.post<ResPage<Container.ContainerInfo>>(
|
||||
`/containers/search`,
|
||||
params,
|
||||
TimeoutEnum.T_40S,
|
||||
currentNode ? { CurrentNode: currentNode } : undefined,
|
||||
);
|
||||
export const searchContainer = (params: Container.ContainerSearch, currentNode?: string, signal?: AbortSignal) => {
|
||||
return http.postWithConfig<ResPage<Container.ContainerInfo>>(`/containers/search`, params, {
|
||||
timeout: TimeoutEnum.T_40S,
|
||||
headers: currentNode ? { CurrentNode: currentNode } : undefined,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
export const listContainer = () => {
|
||||
return http.post<Array<Container.ContainerOption>>(`/containers/list`, {});
|
||||
@@ -44,12 +43,11 @@ export const downloadContainerFile = (params: { containerID: string; path: strin
|
||||
timeout: TimeoutEnum.T_40S,
|
||||
});
|
||||
};
|
||||
export const loadContainerStatus = (currentNode?: string) => {
|
||||
return http.get<Container.ContainerStatus>(
|
||||
`/containers/status`,
|
||||
{},
|
||||
currentNode ? { headers: { CurrentNode: currentNode } } : {},
|
||||
);
|
||||
export const loadContainerStatus = (currentNode?: string, containersOnly = false, signal?: AbortSignal) => {
|
||||
return http.get<Container.ContainerStatus>(`/containers/status`, containersOnly ? { containersOnly: true } : {}, {
|
||||
headers: currentNode ? { CurrentNode: currentNode } : undefined,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
export const loadResourceLimit = () => {
|
||||
return http.get<Container.ResourceLimit>(`/containers/limit`);
|
||||
@@ -90,12 +88,11 @@ export const containerItemStats = (containerID: string, currentNode?: string) =>
|
||||
currentNode ? { CurrentNode: currentNode } : undefined,
|
||||
);
|
||||
};
|
||||
export const containerListStats = (currentNode?: string) => {
|
||||
return http.get<Array<Container.ContainerListStats>>(
|
||||
`/containers/list/stats`,
|
||||
{},
|
||||
currentNode ? { headers: { CurrentNode: currentNode } } : {},
|
||||
);
|
||||
export const containerListStats = (currentNode?: string, ids?: string[], signal?: AbortSignal) => {
|
||||
return http.get<Array<Container.ContainerListStats>>(`/containers/list/stats`, ids ? { ids: ids.join(',') } : {}, {
|
||||
headers: currentNode ? { CurrentNode: currentNode } : undefined,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
export const containerStats = (id: string) => {
|
||||
return http.get<Container.ContainerStats>(`/containers/stats/${id}`);
|
||||
@@ -144,8 +141,13 @@ export const imagePull = (params: Container.ImagePull) => {
|
||||
export const imagePush = (params: Container.ImagePush) => {
|
||||
return http.post<string>(`/containers/image/push`, params);
|
||||
};
|
||||
export const imageLoad = (params: Container.ImageLoad) => {
|
||||
return http.post(`/containers/image/load`, params, TimeoutEnum.T_10M);
|
||||
export const imageLoad = (params: Container.ImageLoad, currentNode?: string) => {
|
||||
return http.post(
|
||||
`/containers/image/load`,
|
||||
params,
|
||||
TimeoutEnum.T_10M,
|
||||
currentNode ? { CurrentNode: currentNode } : undefined,
|
||||
);
|
||||
};
|
||||
export const imageSave = (params: Container.ImageSave) => {
|
||||
return http.post(`/containers/image/save`, params, TimeoutEnum.T_10M);
|
||||
@@ -164,6 +166,9 @@ export const searchNetwork = (params: SearchWithPage) => {
|
||||
export const listNetwork = () => {
|
||||
return http.get<Array<Container.Options>>(`/containers/network`);
|
||||
};
|
||||
export const cleanNetworks = () => {
|
||||
return http.post<{ taskID: string }>(`/containers/network/clean`, {});
|
||||
};
|
||||
export const deleteNetwork = (params: Container.BatchDelete) => {
|
||||
return http.post(`/containers/network/del`, params);
|
||||
};
|
||||
|
||||
@@ -193,8 +193,13 @@ export const redisPersistenceConf = (type: string, database: string) => {
|
||||
export const checkRedisCli = () => {
|
||||
return http.get<boolean>(`/databases/redis/check`);
|
||||
};
|
||||
export const installRedisCli = () => {
|
||||
return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M);
|
||||
export const loadRedisCliStatus = (node: string) => {
|
||||
return http.get<Database.RedisCliStatus>(`/databases/redis/cli/status?operateNode=${encodeURIComponent(node)}`);
|
||||
};
|
||||
export const installRedisCli = (taskID: string, node: string) => {
|
||||
return http.post<Database.RedisCliStatus>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
|
||||
taskID,
|
||||
});
|
||||
};
|
||||
export const changeRedisPassword = (database: string, password: string) => {
|
||||
if (password) {
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
defineOptions({ name: 'TableSearch' });
|
||||
|
||||
const emit = defineEmits(['search', 'update:searchName']);
|
||||
const searchInfo = ref();
|
||||
const route = useRoute();
|
||||
const props = defineProps({
|
||||
placeholder: String,
|
||||
disabled: {
|
||||
@@ -42,6 +44,16 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// Also discard text typed without pressing Enter when leaving a cached tab.
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
searchInfo.value = '';
|
||||
emit('update:searchName', '');
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
|
||||
const search = () => {
|
||||
emit('update:searchName', searchInfo.value);
|
||||
emit('search');
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<fu-table
|
||||
v-if="currentViewMode === 'table'"
|
||||
v-bind="$attrs"
|
||||
:data="props.data"
|
||||
ref="tableRef"
|
||||
@select="handleSelect"
|
||||
@selection-change="handleSelectionChange"
|
||||
@@ -166,16 +167,19 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, useAttrs } from 'vue';
|
||||
import { computed, onActivated, onMounted, ref, useAttrs } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
|
||||
import { useCardColumns } from './useCardColumns';
|
||||
import { useContextMenu } from './useContextMenu';
|
||||
import { useResponsivePagination } from './useResponsivePagination';
|
||||
import { useTableSelection } from './useTableSelection';
|
||||
import { useTablePageState } from './useTablePageState';
|
||||
const slots = useSlots();
|
||||
const attrs = useAttrs();
|
||||
const { isMobile, openMenuTabs } = useGlobalStore();
|
||||
const route = useRoute();
|
||||
const { isMobile, openMenuTabs, currentNode } = useGlobalStore();
|
||||
|
||||
defineOptions({ name: 'ComplexTable' });
|
||||
export type DropdownProps = FuTableOperationButton;
|
||||
@@ -184,6 +188,8 @@ type ViewMode = 'table' | 'card';
|
||||
|
||||
const props = defineProps({
|
||||
header: String,
|
||||
data: { type: Array as PropType<any[]>, default: () => [] },
|
||||
selectionContext: { type: [String, Number, Array, Object, Function], default: undefined },
|
||||
paginationConfig: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -239,7 +245,7 @@ const selectionColumn = computed(() =>
|
||||
columnNodes.value.find((column) => (column.props as Record<string, any> | null)?.type === 'selection'),
|
||||
);
|
||||
const tableData = computed(() => {
|
||||
const data = attrs.data;
|
||||
const data = props.data;
|
||||
return Array.isArray(data) ? data : [];
|
||||
});
|
||||
const cardContentStyle = computed(() => {
|
||||
@@ -295,7 +301,7 @@ const {
|
||||
isDisabled: isRightButtonDisabled,
|
||||
click: rightButtonClick,
|
||||
} = useContextMenu(() => props.rightButtons);
|
||||
const getTableData = () => (Array.isArray(attrs.data) ? attrs.data : []);
|
||||
const getTableData = () => props.data || [];
|
||||
const isRowSelectable = (row: any) => {
|
||||
const selectable = (selectionColumn.value?.props as Record<string, any> | null)?.selectable;
|
||||
return typeof selectable === 'function' ? selectable(row) : true;
|
||||
@@ -304,7 +310,6 @@ const {
|
||||
selectedRows,
|
||||
isRowSelected,
|
||||
clearSelects,
|
||||
pruneSelection,
|
||||
toggleSelection,
|
||||
selectRow,
|
||||
syncTableSelection,
|
||||
@@ -313,7 +318,18 @@ const {
|
||||
handleRowClick,
|
||||
handleKeyDown: handleSelectionKeyDown,
|
||||
handleKeyUp,
|
||||
} = useTableSelection(tableRef, getTableData, (rows) => emit('update:selects', rows), isRowSelectable);
|
||||
} = useTableSelection(
|
||||
tableRef,
|
||||
getTableData,
|
||||
(rows) => emit('update:selects', rows),
|
||||
isRowSelectable,
|
||||
(row) => {
|
||||
const key = attrs.rowKey ?? attrs['row-key'];
|
||||
if (typeof key === 'function') return row == null ? undefined : key(row);
|
||||
if (typeof key === 'string') return key.split('.').reduce((value, part) => value?.[part], row);
|
||||
return row?.id;
|
||||
},
|
||||
);
|
||||
const toggleCardSelection = (row: any, selected: boolean) => {
|
||||
selectRow(row, selected);
|
||||
};
|
||||
@@ -329,10 +345,12 @@ const handleRightClick = (row, column, event) => {
|
||||
};
|
||||
|
||||
function currentChange() {
|
||||
clearSelects();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
function sizeChange() {
|
||||
clearSelects();
|
||||
props.paginationConfig.currentPage = 1;
|
||||
localStorage.setItem(props.paginationConfig.cacheSizeKey, props.paginationConfig.pageSize);
|
||||
emit('search');
|
||||
@@ -446,7 +464,22 @@ watch([currentViewMode, tableData, () => props.syncCardContentHeight], scheduleC
|
||||
flush: 'post',
|
||||
});
|
||||
|
||||
watch(tableData, pruneSelection);
|
||||
useTablePageState(
|
||||
() => props.paginationConfig,
|
||||
() => [
|
||||
route.path,
|
||||
currentNode.value,
|
||||
typeof props.selectionContext === 'function' ? props.selectionContext() : props.selectionContext,
|
||||
],
|
||||
clearSelects,
|
||||
);
|
||||
|
||||
// Kept-alive tabs do not mount again. Reload their reset query on subsequent visits.
|
||||
let hasActivated = false;
|
||||
onActivated(() => {
|
||||
if (hasActivated) emit('search');
|
||||
hasActivated = true;
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', calcHeight);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { watch } from 'vue';
|
||||
|
||||
type Pagination = { currentPage?: number; pageSize?: number } | undefined;
|
||||
|
||||
/** Refreshing data leaves page state alone; only user navigation or a new query clears selection. */
|
||||
export const useTablePageState = (
|
||||
getPagination: () => Pagination,
|
||||
getContext: () => unknown,
|
||||
clearSelection: () => void,
|
||||
) => {
|
||||
watch(() => [getPagination()?.currentPage, getPagination()?.pageSize], clearSelection, { flush: 'sync' });
|
||||
watch(
|
||||
() => JSON.stringify(getContext()),
|
||||
() => {
|
||||
clearSelection();
|
||||
const pagination = getPagination();
|
||||
if (pagination) pagination.currentPage = 1;
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { shallowRef, ref, toRaw, type Ref } from 'vue';
|
||||
import { shallowRef, ref, toRaw, watch, type Ref } from 'vue';
|
||||
|
||||
type TableRef = { refElTable?: any } | undefined;
|
||||
|
||||
@@ -7,6 +7,7 @@ export const useTableSelection = (
|
||||
getTableData: () => any[],
|
||||
onSelectionChange: (rows: any[]) => void,
|
||||
isRowSelectable: (row: any) => boolean = () => true,
|
||||
getRowKey: (row: any) => unknown = (row) => row?.id,
|
||||
) => {
|
||||
const selectedRows = shallowRef<any[]>([]);
|
||||
const shiftPressed = ref(false);
|
||||
@@ -15,8 +16,13 @@ export const useTableSelection = (
|
||||
let isSyncingTableSelection = false;
|
||||
let skipNextSelectionChange = false;
|
||||
|
||||
// Element Plus can return reactive proxies for rows supplied as plain objects.
|
||||
const sameRow = (left: any, right: any) => toRaw(left) === toRaw(right);
|
||||
let isRefreshingData = false;
|
||||
const sameRow = (left: any, right: any) => {
|
||||
if (left == null || right == null) return left === right;
|
||||
const leftKey = getRowKey(left);
|
||||
const rightKey = getRowKey(right);
|
||||
return leftKey != null && rightKey != null ? leftKey === rightKey : toRaw(left) === toRaw(right);
|
||||
};
|
||||
const hasRow = (rows: any[], row: any) => rows.some((item) => sameRow(item, row));
|
||||
const isRowSelected = (row: any) => hasRow(selectedRows.value, row);
|
||||
const getTable = () => tableRef.value?.refElTable;
|
||||
@@ -39,8 +45,12 @@ export const useTableSelection = (
|
||||
const tableData = getTableData();
|
||||
const nextRows = selectedRows.value.filter((row) => hasRow(tableData, row) && isRowSelectable(row));
|
||||
const currentRows = table.getSelectionRows();
|
||||
currentRows.filter((row) => !hasRow(nextRows, row)).forEach((row) => table.toggleRowSelection(row, false));
|
||||
nextRows.filter((row) => !hasRow(currentRows, row)).forEach((row) => table.toggleRowSelection(row, true));
|
||||
currentRows
|
||||
.filter((row) => !nextRows.some((item) => toRaw(item) === toRaw(row)))
|
||||
.forEach((row) => table.toggleRowSelection(row, false));
|
||||
nextRows
|
||||
.filter((row) => !currentRows.some((item) => toRaw(item) === toRaw(row)))
|
||||
.forEach((row) => table.toggleRowSelection(row, true));
|
||||
} finally {
|
||||
isSyncingTableSelection = false;
|
||||
}
|
||||
@@ -73,7 +83,7 @@ export const useTableSelection = (
|
||||
return true;
|
||||
};
|
||||
const handleSelectionChange = (rows: any[]) => {
|
||||
if (isSyncingTableSelection) {
|
||||
if (isSyncingTableSelection || isRefreshingData) {
|
||||
return;
|
||||
}
|
||||
if (skipNextSelectionChange) {
|
||||
@@ -104,16 +114,35 @@ export const useTableSelection = (
|
||||
rangeBaseRows.value = [];
|
||||
};
|
||||
const pruneSelection = () => {
|
||||
const nextRows = selectedRows.value.filter((row) => hasRow(getTableData(), row));
|
||||
if (nextRows.length !== selectedRows.value.length) {
|
||||
const data = getTableData();
|
||||
const nextRows = data.filter((row) => hasRow(selectedRows.value, row) && isRowSelectable(row));
|
||||
if (nextRows.length !== selectedRows.value.length || nextRows.some((row, i) => row !== selectedRows.value[i])) {
|
||||
setSelectedRows(nextRows);
|
||||
}
|
||||
if (lastSelectedRow.value && !hasRow(nextRows, lastSelectedRow.value)) {
|
||||
lastSelectedRow.value = null;
|
||||
rangeBaseRows.value = [];
|
||||
}
|
||||
syncTableSelection();
|
||||
lastSelectedRow.value = nextRows.find((row) => sameRow(row, lastSelectedRow.value)) ?? null;
|
||||
rangeBaseRows.value = lastSelectedRow.value ? nextRows.filter((row) => hasRow(rangeBaseRows.value, row)) : [];
|
||||
};
|
||||
// Guard before the child table reacts to replacement data and emits an empty selection.
|
||||
watch(
|
||||
getTableData,
|
||||
() => {
|
||||
isRefreshingData = true;
|
||||
skipNextSelectionChange = false;
|
||||
pruneSelection();
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
watch(
|
||||
getTableData,
|
||||
() => {
|
||||
try {
|
||||
syncTableSelection();
|
||||
} finally {
|
||||
isRefreshingData = false;
|
||||
}
|
||||
},
|
||||
{ flush: 'post' },
|
||||
);
|
||||
const toggleSelection = () => {
|
||||
const selectableRows = getTableData().filter(isRowSelectable);
|
||||
const allSelected = selectableRows.length > 0 && selectableRows.every(isRowSelected);
|
||||
|
||||
@@ -134,10 +134,11 @@ function initChart() {
|
||||
series.push({
|
||||
name: item?.name,
|
||||
type: 'line',
|
||||
itemStyle: seriesStyle[index + 2],
|
||||
areaStyle: seriesStyle[index],
|
||||
itemStyle: item.itemStyle ?? seriesStyle[index + 2],
|
||||
areaStyle: item.areaStyle ?? seriesStyle[index],
|
||||
data: item?.data,
|
||||
showSymbol: false,
|
||||
showSymbol: item.showSymbol ?? false,
|
||||
symbolSize: item.symbolSize,
|
||||
yAxisIndex: item.yAxisIndex ? 1 : null,
|
||||
});
|
||||
});
|
||||
@@ -307,7 +308,9 @@ function initChart() {
|
||||
series: series,
|
||||
dataZoom: [
|
||||
{
|
||||
...(props.option.xData?.[0] ? { startValue: props.option.xData[0] } : {}),
|
||||
...((props.option.xAxis?.min ?? props.option.xData?.[0]) != null
|
||||
? { startValue: props.option.xAxis?.min ?? props.option.xData[0] }
|
||||
: {}),
|
||||
left: grid.left,
|
||||
right: grid.right,
|
||||
show: props.dataZoom,
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { reactive, type UnwrapNestedRefs } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { getPageState } from '@/utils/page-state-cache';
|
||||
|
||||
/** A new page visit starts from its defaults instead of restoring the previous page number. */
|
||||
export const usePageState = <T extends object>(factory: () => T): UnwrapNestedRefs<T> => {
|
||||
const route = useRoute();
|
||||
const { currentNode } = useGlobalStore();
|
||||
const key = `${currentNode.value}:${String(route.name || route.path)}`;
|
||||
return reactive(getPageState(key, factory));
|
||||
return reactive(factory());
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import GlobalStore from '@/store/modules/global';
|
||||
import router from '@/routers/router';
|
||||
import { bindSearchReset } from '@/utils/search-reset';
|
||||
|
||||
/** Kept as a compatible entry point; searches now reset on tab, node or resource changes. */
|
||||
export const useSearchPersistence = (
|
||||
page: string,
|
||||
fields: Parameters<typeof bindSearchReset>[1],
|
||||
scope?: () => string | number | undefined,
|
||||
) => {
|
||||
const { currentNode } = storeToRefs(GlobalStore());
|
||||
return bindSearchReset(() => [router.currentRoute.value.path, currentNode.value, page, scope?.()], fields);
|
||||
};
|
||||
@@ -739,6 +739,11 @@ const message = {
|
||||
keyspaceMisses: 'Number of failed attempts to find the database key.',
|
||||
hit: 'Find the database key hit ratio.',
|
||||
latestForkUsec: 'The number of microseconds spent on the last fork() operation.',
|
||||
redisCliEnableTask: 'Enable remote Redis terminal',
|
||||
redisCliEnabling: 'Enabling remote Redis terminal',
|
||||
redisCliEnabled: 'Remote Redis terminal enabled',
|
||||
redisCliEnableFailed: 'Failed to enable remote Redis terminal. Check the task log and retry.',
|
||||
redisCliTaskSubmitted: 'Task submitted. View progress in the task center.',
|
||||
redisCliHelper: '"redis-cli" not detected. Enable the service first.',
|
||||
redisQuickCmd: 'Redis quick commands',
|
||||
recoverHelper: 'This will overwrite data with [{0}]. Continue?',
|
||||
@@ -1508,6 +1513,59 @@ const message = {
|
||||
extraHeaders: 'Extra Headers',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'Memory read/write activity',
|
||||
hotspotTemperature: 'Hotspot temperature',
|
||||
fanRPM: 'Fan speed (RPM)',
|
||||
eccCorrectable: 'Correctable errors',
|
||||
eccUncorrectable: 'Uncorrectable errors',
|
||||
eccPending: 'Pending ECC mode',
|
||||
engineUtil: 'Engine utilization',
|
||||
cpuUtil: 'CPU · utilization',
|
||||
aiCore: 'AICore · utilization',
|
||||
aiCPUUtil: 'AICPU · utilization',
|
||||
ctrlCPUUtil: 'CtrlCPU · utilization',
|
||||
mediaFrequency: 'Media · frequency',
|
||||
encoderUtil: 'Encoder · utilization',
|
||||
decoderUtil: 'Decoder · utilization',
|
||||
jpegUtil: 'JPEG · utilization',
|
||||
ofaUtil: 'OFA · utilization',
|
||||
mediaUtil: 'Media · utilization',
|
||||
computeUtil: 'Compute · utilization',
|
||||
copyUtil: 'Copy · utilization',
|
||||
ddrUsage: 'DDR · capacity',
|
||||
ddrUsed: 'DDR · used',
|
||||
ddrTotal: 'DDR · total',
|
||||
ddrBandwidth: 'DDR · bandwidth utilization',
|
||||
hbmUsage: 'HBM · capacity',
|
||||
hbmUsed: 'HBM · used',
|
||||
hbmTotal: 'HBM · total',
|
||||
hbmBandwidth: 'HBM · bandwidth utilization',
|
||||
hugepagesUsage: 'Huge pages · count',
|
||||
hugepagesUsed: 'Huge pages · used',
|
||||
hugepagesTotal: 'Huge pages · total',
|
||||
|
||||
historyAverage: 'Average',
|
||||
historyPeak: 'Peak',
|
||||
historyAggregated:
|
||||
'{count} samples aggregated into {seconds}-second intervals; processes show counts only.',
|
||||
historyRaw: '{count} original samples.',
|
||||
historyMetricEmpty: 'No data for this metric in the selected time range',
|
||||
historyLoadFailed: 'Failed to load history. Please retry.',
|
||||
collectedAt: 'Collected at',
|
||||
processUnavailable: 'Process information is unavailable',
|
||||
architecture: 'Architecture',
|
||||
memoryFrequency: 'Memory frequency',
|
||||
memoryTemperature: 'Memory temperature',
|
||||
memoryReserved: 'Reserved memory',
|
||||
powerDefaultLimit: 'Default power limit',
|
||||
powerMaxLimit: 'Maximum configurable power limit',
|
||||
clockEvents: 'Clock limiting reasons',
|
||||
memoryBandwidth: 'Memory bandwidth utilization',
|
||||
legacyDevice: 'Legacy record',
|
||||
legacyHistory:
|
||||
'These records have no stable device ID. Their identity and missing values cannot be verified.',
|
||||
pcieGeneration: 'PCIe generation (current / maximum)',
|
||||
pcieWidth: 'PCIe width (current / maximum)',
|
||||
gpu: 'GPU Monitoring',
|
||||
gpuHelper: 'No supported GPU/XPU/NPU management command was detected. Please check and try again!',
|
||||
process: 'Process Information',
|
||||
@@ -1523,7 +1581,7 @@ const message = {
|
||||
temperature: 'Temperature',
|
||||
performanceState: 'Performance State',
|
||||
powerUsage: 'Power Consumption',
|
||||
memoryUsage: 'Memory Utilization',
|
||||
memoryUsage: 'Memory capacity usage',
|
||||
fanSpeed: 'Fan Speed',
|
||||
power: 'Power',
|
||||
powerCurrent: 'Current Power',
|
||||
@@ -1788,6 +1846,11 @@ const message = {
|
||||
imageName: 'Image name',
|
||||
pull: 'Pull',
|
||||
path: 'Path',
|
||||
imageImportSource: 'Import source',
|
||||
imageUploadDrop: 'Drop an image file here, or click to upload',
|
||||
imageUploadHelper:
|
||||
'Any file format is allowed. Docker validates the contents. Uploaded files are retained for retrying imports or manual deletion.',
|
||||
imageImportSubmitted: 'Image import task submitted. Check the task log for the result.',
|
||||
importImage: 'Import',
|
||||
buildArgs: 'Build Arguments',
|
||||
imageBuild: 'Build',
|
||||
@@ -6723,6 +6786,15 @@ const message = {
|
||||
snapshot: 'System Snapshot',
|
||||
clamsRulesHelper: 'Virus scanning tasks that require alerts',
|
||||
cronJobRulesHelper: 'This type of scheduled task needs to be configured',
|
||||
alertTriggerMode: 'Notify on',
|
||||
alertTriggerFailed: 'Failure only',
|
||||
alertTriggerSuccess: 'Success only',
|
||||
alertTriggerBoth: 'Success or failure',
|
||||
alertNotificationHelper:
|
||||
'Notify after the final execution result, not during retries. Success and failure notifications share the daily limit and sending hours.',
|
||||
notificationTitle: 'Scheduled task - {0} 「{1}」 execution notification',
|
||||
notificationSuccessTitle: 'Scheduled task - {0} 「{1}」 completed successfully',
|
||||
notificationRule: '{0}: {1}, up to {2} notifications per day',
|
||||
clamsTitle: 'Virus scan task 「 {0} 」 detected infected file alert',
|
||||
cronJobAppTitle: 'Cronjob - Backup App 「 {0} 」 Task Failure Alert',
|
||||
cronJobWebsiteTitle: 'Cronjob - Backup Website「 {0} 」Task Failure Alert',
|
||||
|
||||
@@ -1517,6 +1517,59 @@ const message = {
|
||||
extraHeaders: 'Encabezados adicionales',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'Actividad de lectura/escritura de memoria',
|
||||
hotspotTemperature: 'Temperatura del punto caliente',
|
||||
fanRPM: 'Velocidad del ventilador (RPM)',
|
||||
eccCorrectable: 'Errores corregibles',
|
||||
eccUncorrectable: 'Errores no corregibles',
|
||||
eccPending: 'Modo ECC pendiente',
|
||||
engineUtil: 'Utilización de motores',
|
||||
cpuUtil: 'CPU · utilización',
|
||||
aiCore: 'AICore · utilización',
|
||||
aiCPUUtil: 'AICPU · utilización',
|
||||
ctrlCPUUtil: 'CtrlCPU · utilización',
|
||||
mediaFrequency: 'Medios · frecuencia',
|
||||
encoderUtil: 'Codificador · utilización',
|
||||
decoderUtil: 'Decodificador · utilización',
|
||||
jpegUtil: 'JPEG · utilización',
|
||||
ofaUtil: 'OFA · utilización',
|
||||
mediaUtil: 'Medios · utilización',
|
||||
computeUtil: 'Cómputo · utilización',
|
||||
copyUtil: 'Copia · utilización',
|
||||
ddrUsage: 'DDR · capacidad',
|
||||
ddrUsed: 'DDR · usado',
|
||||
ddrTotal: 'DDR · total',
|
||||
ddrBandwidth: 'DDR · utilización del ancho de banda',
|
||||
hbmUsage: 'HBM · capacidad',
|
||||
hbmUsed: 'HBM · usado',
|
||||
hbmTotal: 'HBM · total',
|
||||
hbmBandwidth: 'HBM · utilización del ancho de banda',
|
||||
hugepagesUsage: 'Páginas grandes · cantidad',
|
||||
hugepagesUsed: 'Páginas grandes · usado',
|
||||
hugepagesTotal: 'Páginas grandes · total',
|
||||
|
||||
historyAverage: 'Promedio',
|
||||
historyPeak: 'Máximo',
|
||||
historyAggregated:
|
||||
'{count} muestras agrupadas en intervalos de {seconds} segundos; los procesos solo muestran cantidades.',
|
||||
historyRaw: '{count} muestras originales.',
|
||||
historyMetricEmpty: 'No hay datos de esta métrica en el período seleccionado',
|
||||
historyLoadFailed: 'No se pudo cargar el historial. Inténtelo de nuevo.',
|
||||
collectedAt: 'Fecha de recopilación',
|
||||
processUnavailable: 'Información de procesos no disponible',
|
||||
architecture: 'Arquitectura',
|
||||
memoryFrequency: 'Frecuencia de memoria',
|
||||
memoryTemperature: 'Temperatura de memoria',
|
||||
memoryReserved: 'Memoria reservada',
|
||||
powerDefaultLimit: 'Límite de potencia predeterminado',
|
||||
powerMaxLimit: 'Potencia máxima configurable',
|
||||
clockEvents: 'Motivos de limitación de frecuencia',
|
||||
memoryBandwidth: 'Uso del ancho de banda de memoria',
|
||||
legacyDevice: 'Registro antiguo',
|
||||
legacyHistory:
|
||||
'Estos registros no tienen un ID de dispositivo estable. No se pueden verificar su identidad ni los valores ausentes.',
|
||||
pcieGeneration: 'Generación PCIe (actual / máxima)',
|
||||
pcieWidth: 'Ancho PCIe (actual / máximo)',
|
||||
gpu: 'Monitoreo de GPU',
|
||||
gpuHelper:
|
||||
'No se detectó ningún comando de administración de GPU/XPU/NPU compatible. ¡Compruebe e inténtelo de nuevo!',
|
||||
@@ -1532,7 +1585,7 @@ const message = {
|
||||
temperature: 'Temperatura',
|
||||
performanceState: 'Estado de Rendimiento',
|
||||
powerUsage: 'Consumo de Energía',
|
||||
memoryUsage: 'Utilización de Memoria',
|
||||
memoryUsage: 'Uso de capacidad de memoria',
|
||||
fanSpeed: 'Velocidad del Ventilador',
|
||||
power: 'Energía',
|
||||
powerCurrent: 'Energía Actual',
|
||||
@@ -6835,6 +6888,15 @@ const message = {
|
||||
snapshot: 'Snapshot del sistema',
|
||||
clamsRulesHelper: 'Tareas de escaneo antivirus que requieren alerta',
|
||||
cronJobRulesHelper: 'Este tipo de tareas programadas necesita configuración',
|
||||
alertTriggerMode: 'Notificar cuando',
|
||||
alertTriggerFailed: 'Solo falle',
|
||||
alertTriggerSuccess: 'Solo tenga éxito',
|
||||
alertTriggerBoth: 'Tenga éxito o falle',
|
||||
alertNotificationHelper:
|
||||
'Notificar al finalizar la ejecución, no durante los reintentos. Las notificaciones de éxito y fallo comparten el límite diario y el horario de envío.',
|
||||
notificationTitle: 'Tarea programada - {0} 「{1}」 notificación de ejecución',
|
||||
notificationSuccessTitle: 'Tarea programada - {0} 「{1}」 completada correctamente',
|
||||
notificationRule: '{0}: {1}, hasta {2} notificaciones al día',
|
||||
clamsTitle: 'Tarea antivirus 「 {0} 」 detectó archivo infectado',
|
||||
cronJobAppTitle: 'Cronjob - Backup de app 「 {0} 」 falló',
|
||||
cronJobWebsiteTitle: 'Cronjob - Backup de sitio 「 {0} 」 falló',
|
||||
|
||||
@@ -1493,6 +1493,58 @@ const message = {
|
||||
extraHeaders: 'هدرهای اضافی',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'فعالیت خواندن/نوشتن حافظه',
|
||||
hotspotTemperature: 'دمای نقطه داغ',
|
||||
fanRPM: 'سرعت فن (RPM)',
|
||||
eccCorrectable: 'خطاهای قابل اصلاح',
|
||||
eccUncorrectable: 'خطاهای غیرقابل اصلاح',
|
||||
eccPending: 'حالت ECC در انتظار اعمال',
|
||||
engineUtil: 'میزان استفاده از موتور',
|
||||
cpuUtil: 'CPU · میزان استفاده',
|
||||
aiCore: 'AICore · میزان استفاده',
|
||||
aiCPUUtil: 'AICPU · میزان استفاده',
|
||||
ctrlCPUUtil: 'CtrlCPU · میزان استفاده',
|
||||
mediaFrequency: 'رسانه · فرکانس',
|
||||
encoderUtil: 'رمزگذار · میزان استفاده',
|
||||
decoderUtil: 'رمزگشا · میزان استفاده',
|
||||
jpegUtil: 'JPEG · میزان استفاده',
|
||||
ofaUtil: 'OFA · میزان استفاده',
|
||||
mediaUtil: 'رسانه · میزان استفاده',
|
||||
computeUtil: 'محاسبه · میزان استفاده',
|
||||
copyUtil: 'کپی · میزان استفاده',
|
||||
ddrUsage: 'DDR · ظرفیت',
|
||||
ddrUsed: 'DDR · استفادهشده',
|
||||
ddrTotal: 'DDR · کل',
|
||||
ddrBandwidth: 'DDR · میزان استفاده از پهنای باند',
|
||||
hbmUsage: 'HBM · ظرفیت',
|
||||
hbmUsed: 'HBM · استفادهشده',
|
||||
hbmTotal: 'HBM · کل',
|
||||
hbmBandwidth: 'HBM · میزان استفاده از پهنای باند',
|
||||
hugepagesUsage: 'صفحات بزرگ · تعداد',
|
||||
hugepagesUsed: 'صفحات بزرگ · استفادهشده',
|
||||
hugepagesTotal: 'صفحات بزرگ · کل',
|
||||
|
||||
historyAverage: 'میانگین',
|
||||
historyPeak: 'بیشینه',
|
||||
historyAggregated:
|
||||
'{count} نمونه در بازههای {seconds} ثانیهای تجمیع شدهاند؛ فقط تعداد فرایندها نمایش داده میشود.',
|
||||
historyRaw: '{count} نمونه اصلی.',
|
||||
historyMetricEmpty: 'در بازه زمانی انتخابشده دادهای برای این شاخص وجود ندارد',
|
||||
historyLoadFailed: 'بارگیری سابقه ناموفق بود. دوباره تلاش کنید.',
|
||||
collectedAt: 'زمان گردآوری',
|
||||
processUnavailable: 'اطلاعات فرایند در دسترس نیست',
|
||||
architecture: 'معماری',
|
||||
memoryFrequency: 'فرکانس حافظه',
|
||||
memoryTemperature: 'دمای حافظه',
|
||||
memoryReserved: 'حافظه رزروشده',
|
||||
powerDefaultLimit: 'محدودیت توان پیشفرض',
|
||||
powerMaxLimit: 'حداکثر توان قابل تنظیم',
|
||||
clockEvents: 'دلایل محدودیت فرکانس',
|
||||
memoryBandwidth: 'میزان استفاده از پهنای باند حافظه',
|
||||
legacyDevice: 'رکورد قدیمی',
|
||||
legacyHistory: 'این رکوردها شناسه ثابت دستگاه ندارند. هویت دستگاه و مقادیر مفقود قابل تأیید نیستند.',
|
||||
pcieGeneration: 'نسل PCIe (فعلی / حداکثر)',
|
||||
pcieWidth: 'عرض PCIe (فعلی / حداکثر)',
|
||||
gpu: 'نظارت بر GPU',
|
||||
gpuHelper: 'هیچ دستور مدیریت GPU/XPU/NPU پشتیبانیشدهای شناسایی نشد. لطفاً بررسی و دوباره تلاش کنید!',
|
||||
process: 'اطلاعات فرآیند',
|
||||
@@ -1508,7 +1560,7 @@ const message = {
|
||||
temperature: 'دما',
|
||||
performanceState: 'وضعیت عملکرد',
|
||||
powerUsage: 'مصرف برق',
|
||||
memoryUsage: 'استفاده از حافظه',
|
||||
memoryUsage: 'میزان اشغال ظرفیت حافظه',
|
||||
fanSpeed: 'سرعت فن',
|
||||
power: 'برق',
|
||||
powerCurrent: 'برق فعلی',
|
||||
@@ -6677,6 +6729,15 @@ const message = {
|
||||
snapshot: 'تصویر لحظهای سیستم',
|
||||
clamsRulesHelper: 'وظایف اسکن ویروس که نیاز به هشدار دارند',
|
||||
cronJobRulesHelper: 'این نوع وظیفه زمانبندی باید پیکربندی شود',
|
||||
alertTriggerMode: 'شرط اعلان',
|
||||
alertTriggerFailed: 'فقط شکست',
|
||||
alertTriggerSuccess: 'فقط موفقیت',
|
||||
alertTriggerBoth: 'موفقیت یا شکست',
|
||||
alertNotificationHelper:
|
||||
'اعلان پس از نتیجه نهایی اجرا ارسال میشود، نه هنگام تلاش مجدد. اعلانهای موفقیت و شکست سقف روزانه و ساعات ارسال مشترک دارند.',
|
||||
notificationTitle: 'وظیفه زمانبندی - {0} «{1}» اعلان اجرا',
|
||||
notificationSuccessTitle: 'وظیفه زمانبندی - {0} «{1}» با موفقیت انجام شد',
|
||||
notificationRule: '{0}: {1}، حداکثر {2} اعلان در روز',
|
||||
clamsTitle: 'وظیفه اسکن ویروس « {0} » هشدار فایل آلوده شناسایی شد',
|
||||
cronJobAppTitle: 'وظیفه زمانبندی - پشتیبانگیری از برنامه « {0} » هشدار شکست وظیفه',
|
||||
cronJobWebsiteTitle: 'وظیفه زمانبندی - پشتیبانگیری از وبسایت « {0} » هشدار شکست وظیفه',
|
||||
|
||||
@@ -1496,6 +1496,57 @@ const message = {
|
||||
extraHeaders: '追加ヘッダー',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'メモリ読み書き稼働率',
|
||||
hotspotTemperature: 'ホットスポット温度',
|
||||
fanRPM: 'ファン回転数(RPM)',
|
||||
eccCorrectable: '訂正可能なエラー数',
|
||||
eccUncorrectable: '訂正不能なエラー数',
|
||||
eccPending: '適用待ちの ECC モード',
|
||||
engineUtil: 'エンジン使用率',
|
||||
cpuUtil: 'CPU · 使用率',
|
||||
aiCore: 'AICore · 使用率',
|
||||
aiCPUUtil: 'AICPU · 使用率',
|
||||
ctrlCPUUtil: 'CtrlCPU · 使用率',
|
||||
mediaFrequency: 'メディア · 周波数',
|
||||
encoderUtil: 'エンコーダー · 使用率',
|
||||
decoderUtil: 'デコーダー · 使用率',
|
||||
jpegUtil: 'JPEG · 使用率',
|
||||
ofaUtil: 'OFA · 使用率',
|
||||
mediaUtil: 'メディア · 使用率',
|
||||
computeUtil: '計算 · 使用率',
|
||||
copyUtil: 'コピー · 使用率',
|
||||
ddrUsage: 'DDR · 容量',
|
||||
ddrUsed: 'DDR · 使用量',
|
||||
ddrTotal: 'DDR · 総容量',
|
||||
ddrBandwidth: 'DDR · 帯域使用率',
|
||||
hbmUsage: 'HBM · 容量',
|
||||
hbmUsed: 'HBM · 使用量',
|
||||
hbmTotal: 'HBM · 総容量',
|
||||
hbmBandwidth: 'HBM · 帯域使用率',
|
||||
hugepagesUsage: 'ヒュージページ · 数',
|
||||
hugepagesUsed: 'ヒュージページ · 使用量',
|
||||
hugepagesTotal: 'ヒュージページ · 総容量',
|
||||
|
||||
historyAverage: '平均値',
|
||||
historyPeak: '最大値',
|
||||
historyAggregated: '{count} 件のサンプルを {seconds} 秒間隔で集計。プロセスは件数のみ表示します。',
|
||||
historyRaw: '元のサンプル {count} 件。',
|
||||
historyMetricEmpty: '選択した期間にこの指標のデータはありません',
|
||||
historyLoadFailed: '履歴の読み込みに失敗しました。再試行してください。',
|
||||
collectedAt: '収集日時',
|
||||
processUnavailable: 'プロセス情報を取得できません',
|
||||
architecture: 'アーキテクチャ',
|
||||
memoryFrequency: 'メモリ周波数',
|
||||
memoryTemperature: 'メモリ温度',
|
||||
memoryReserved: '予約済みメモリ',
|
||||
powerDefaultLimit: '既定の電力制限',
|
||||
powerMaxLimit: '設定可能な最大電力',
|
||||
clockEvents: 'クロック制限の理由',
|
||||
memoryBandwidth: 'メモリ帯域幅使用率',
|
||||
legacyDevice: '旧記録',
|
||||
legacyHistory: 'これらの記録には固定のデバイス ID がなく、デバイスの識別や欠損値を確認できません。',
|
||||
pcieGeneration: 'PCIe 世代(現在 / 最大)',
|
||||
pcieWidth: 'PCIe 幅(現在 / 最大)',
|
||||
gpu: 'GPU 監視',
|
||||
gpuHelper: '対応する GPU/XPU/NPU 管理コマンドが検出されませんでした。確認して再試行してください!',
|
||||
process: 'プロセス情報',
|
||||
@@ -1510,7 +1561,7 @@ const message = {
|
||||
temperature: '温度',
|
||||
performanceState: 'パフォーマンス状態',
|
||||
powerUsage: '消費電力',
|
||||
memoryUsage: 'メモリ使用率',
|
||||
memoryUsage: 'メモリ容量使用率',
|
||||
fanSpeed: 'ファン速度',
|
||||
power: '電力',
|
||||
powerCurrent: '現在の電力',
|
||||
@@ -6717,6 +6768,15 @@ const message = {
|
||||
snapshot: 'システムスナップショット',
|
||||
clamsRulesHelper: 'アラートが必要なウイルススキャンタスク',
|
||||
cronJobRulesHelper: 'このタイプのスケジュールタスクには設定が必要です',
|
||||
alertTriggerMode: '通知条件',
|
||||
alertTriggerFailed: '失敗時のみ',
|
||||
alertTriggerSuccess: '成功時のみ',
|
||||
alertTriggerBoth: '成功時または失敗時',
|
||||
alertNotificationHelper:
|
||||
'再試行中ではなく、最終的な実行結果に基づいて通知します。成功と失敗の通知は、1日の上限と送信時間帯を共有します。',
|
||||
notificationTitle: '定期タスク - {0}「{1}」実行通知',
|
||||
notificationSuccessTitle: '定期タスク - {0}「{1}」が正常に完了しました',
|
||||
notificationRule: '{0}:{1}、1日最大 {2} 回通知',
|
||||
clamsTitle: 'ウイルススキャンタスク「{0}」が感染ファイルを検出したアラート',
|
||||
cronJobAppTitle: 'Cronジョブ - アプリバックアップ「{0}」タスク失敗アラート',
|
||||
cronJobWebsiteTitle: 'Cronジョブ - ウェブサイトバックアップ「{0}」タスク失敗アラート',
|
||||
|
||||
@@ -1482,6 +1482,57 @@ const message = {
|
||||
extraHeaders: '추가 헤더',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: '메모리 읽기/쓰기 활성률',
|
||||
hotspotTemperature: '핫스폿 온도',
|
||||
fanRPM: '팬 회전 속도 (RPM)',
|
||||
eccCorrectable: '수정 가능한 오류 수',
|
||||
eccUncorrectable: '수정 불가능한 오류 수',
|
||||
eccPending: '적용 대기 중인 ECC 모드',
|
||||
engineUtil: '엔진 사용률',
|
||||
cpuUtil: 'CPU · 사용률',
|
||||
aiCore: 'AICore · 사용률',
|
||||
aiCPUUtil: 'AICPU · 사용률',
|
||||
ctrlCPUUtil: 'CtrlCPU · 사용률',
|
||||
mediaFrequency: '미디어 · 주파수',
|
||||
encoderUtil: '인코더 · 사용률',
|
||||
decoderUtil: '디코더 · 사용률',
|
||||
jpegUtil: 'JPEG · 사용률',
|
||||
ofaUtil: 'OFA · 사용률',
|
||||
mediaUtil: '미디어 · 사용률',
|
||||
computeUtil: '컴퓨팅 · 사용률',
|
||||
copyUtil: '복사 · 사용률',
|
||||
ddrUsage: 'DDR · 용량',
|
||||
ddrUsed: 'DDR · 사용량',
|
||||
ddrTotal: 'DDR · 총량',
|
||||
ddrBandwidth: 'DDR · 대역폭 사용률',
|
||||
hbmUsage: 'HBM · 용량',
|
||||
hbmUsed: 'HBM · 사용량',
|
||||
hbmTotal: 'HBM · 총량',
|
||||
hbmBandwidth: 'HBM · 대역폭 사용률',
|
||||
hugepagesUsage: '대용량 페이지 · 수',
|
||||
hugepagesUsed: '대용량 페이지 · 사용량',
|
||||
hugepagesTotal: '대용량 페이지 · 총량',
|
||||
|
||||
historyAverage: '평균',
|
||||
historyPeak: '최댓값',
|
||||
historyAggregated: '샘플 {count}개를 {seconds}초 간격으로 집계합니다. 프로세스는 개수만 표시합니다.',
|
||||
historyRaw: '원본 샘플 {count}개.',
|
||||
historyMetricEmpty: '선택한 기간에 이 지표의 데이터가 없습니다',
|
||||
historyLoadFailed: '기록을 불러오지 못했습니다. 다시 시도하세요.',
|
||||
collectedAt: '수집 시간',
|
||||
processUnavailable: '프로세스 정보를 사용할 수 없습니다',
|
||||
architecture: '아키텍처',
|
||||
memoryFrequency: '메모리 주파수',
|
||||
memoryTemperature: '메모리 온도',
|
||||
memoryReserved: '예약된 메모리',
|
||||
powerDefaultLimit: '기본 전력 제한',
|
||||
powerMaxLimit: '설정 가능한 최대 전력',
|
||||
clockEvents: '클럭 제한 이유',
|
||||
memoryBandwidth: '메모리 대역폭 사용률',
|
||||
legacyDevice: '이전 기록',
|
||||
legacyHistory: '이 기록에는 고정 장치 ID가 없어 장치 식별 및 누락 값을 확인할 수 없습니다.',
|
||||
pcieGeneration: 'PCIe 세대 (현재 / 최대)',
|
||||
pcieWidth: 'PCIe 너비 (현재 / 최대)',
|
||||
gpu: 'GPU 모니터링',
|
||||
gpuHelper: '지원되는 GPU/XPU/NPU 관리 명령을 감지하지 못했습니다. 확인하고 다시 시도하세요!',
|
||||
process: '프로세스 정보',
|
||||
@@ -1496,7 +1547,7 @@ const message = {
|
||||
temperature: '온도',
|
||||
performanceState: '성능 상태',
|
||||
powerUsage: '전력 소비',
|
||||
memoryUsage: '메모리 사용률',
|
||||
memoryUsage: '메모리 용량 사용률',
|
||||
fanSpeed: '팬 속도',
|
||||
power: '전력',
|
||||
powerCurrent: '현재 전력',
|
||||
@@ -6582,6 +6633,15 @@ const message = {
|
||||
snapshot: '시스템 스냅샷',
|
||||
clamsRulesHelper: '알림이 필요한 바이러스 검사 작업',
|
||||
cronJobRulesHelper: '이 유형의 예약된 작업은 구성해야 합니다',
|
||||
alertTriggerMode: '알림 조건',
|
||||
alertTriggerFailed: '실패 시에만',
|
||||
alertTriggerSuccess: '성공 시에만',
|
||||
alertTriggerBoth: '성공 또는 실패 시',
|
||||
alertNotificationHelper:
|
||||
'재시도 중이 아닌 최종 실행 결과에 따라 알립니다. 성공 및 실패 알림은 일일 한도와 전송 시간대를 공유합니다.',
|
||||
notificationTitle: '예약 작업 - {0} 「{1}」 실행 알림',
|
||||
notificationSuccessTitle: '예약 작업 - {0} 「{1}」 실행 성공',
|
||||
notificationRule: '{0}: {1}, 하루 최대 {2}회 알림',
|
||||
clamsTitle: '바이러스 검사 작업 「 {0} 」 감염된 파일 알림',
|
||||
cronJobAppTitle: '크론 작업 - 백업 애플리케이션 「 {0} 」 작업 실패 알림',
|
||||
cronJobWebsiteTitle: '크론 작업 - 백업 웹사이트「 {0} 」작업 실패 알림',
|
||||
|
||||
@@ -1475,6 +1475,57 @@ const message = {
|
||||
extraHeaders: 'Headers ເພີ່ມເຕີມ',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'ກິດຈະກຳອ່ານ/ຂຽນໜ່ວຍຄວາມຈຳ',
|
||||
hotspotTemperature: 'ອຸນຫະພູມຈຸດຮ້ອນ',
|
||||
fanRPM: 'ຄວາມໄວພັດລົມ (RPM)',
|
||||
eccCorrectable: 'ຂໍ້ຜິດພາດທີ່ແກ້ໄຂໄດ້',
|
||||
eccUncorrectable: 'ຂໍ້ຜິດພາດທີ່ແກ້ໄຂບໍ່ໄດ້',
|
||||
eccPending: 'ໂໝດ ECC ທີ່ລໍຖ້າ',
|
||||
engineUtil: 'ອັດຕາການໃຊ້ເຄື່ອງຈັກ',
|
||||
cpuUtil: 'CPU · ອັດຕາການໃຊ້',
|
||||
aiCore: 'AICore · ອັດຕາການໃຊ້',
|
||||
aiCPUUtil: 'AICPU · ອັດຕາການໃຊ້',
|
||||
ctrlCPUUtil: 'CtrlCPU · ອັດຕາການໃຊ້',
|
||||
mediaFrequency: 'ສື່ · ຄວາມຖີ່',
|
||||
encoderUtil: 'ຕົວເຂົ້າລະຫັດ · ອັດຕາການໃຊ້',
|
||||
decoderUtil: 'ຕົວຖອດລະຫັດ · ອັດຕາການໃຊ້',
|
||||
jpegUtil: 'JPEG · ອັດຕາການໃຊ້',
|
||||
ofaUtil: 'OFA · ອັດຕາການໃຊ້',
|
||||
mediaUtil: 'ສື່ · ອັດຕາການໃຊ້',
|
||||
computeUtil: 'ຄຳນວນ · ອັດຕາການໃຊ້',
|
||||
copyUtil: 'ສຳເນົາ · ອັດຕາການໃຊ້',
|
||||
ddrUsage: 'DDR · ຄວາມຈຸ',
|
||||
ddrUsed: 'DDR · ໃຊ້ແລ້ວ',
|
||||
ddrTotal: 'DDR · ທັງໝົດ',
|
||||
ddrBandwidth: 'DDR · ອັດຕາການໃຊ້ແບນວິດ',
|
||||
hbmUsage: 'HBM · ຄວາມຈຸ',
|
||||
hbmUsed: 'HBM · ໃຊ້ແລ້ວ',
|
||||
hbmTotal: 'HBM · ທັງໝົດ',
|
||||
hbmBandwidth: 'HBM · ອັດຕາການໃຊ້ແບນວິດ',
|
||||
hugepagesUsage: 'ໜ້າຂະໜາດໃຫຍ່ · ຈຳນວນ',
|
||||
hugepagesUsed: 'ໜ້າຂະໜາດໃຫຍ່ · ໃຊ້ແລ້ວ',
|
||||
hugepagesTotal: 'ໜ້າຂະໜາດໃຫຍ່ · ທັງໝົດ',
|
||||
|
||||
historyAverage: 'ຄ່າສະເລ່ຍ',
|
||||
historyPeak: 'ຄ່າສູງສຸດ',
|
||||
historyAggregated: 'ລວມ {count} ຕົວຢ່າງເປັນຊ່ວງ {seconds} ວິນາທີ; ສະແດງພຽງຈຳນວນໂປຣເຊສ.',
|
||||
historyRaw: '{count} ຕົວຢ່າງຕົ້ນສະບັບ.',
|
||||
historyMetricEmpty: 'ບໍ່ມີຂໍ້ມູນຕົວຊີ້ວັດນີ້ໃນຊ່ວງເວລາທີ່ເລືອກ',
|
||||
historyLoadFailed: 'ໂຫຼດປະຫວັດບໍ່ສຳເລັດ. ກະລຸນາລອງໃໝ່.',
|
||||
collectedAt: 'ເວລາເກັບຂໍ້ມູນ',
|
||||
processUnavailable: 'ບໍ່ມີຂໍ້ມູນໂປຣເຊສ',
|
||||
architecture: 'ສະຖາປັດຕະຍະກຳ',
|
||||
memoryFrequency: 'ຄວາມຖີ່ໜ່ວຍຄວາມຈຳ',
|
||||
memoryTemperature: 'ອຸນຫະພູມໜ່ວຍຄວາມຈຳ',
|
||||
memoryReserved: 'ໜ່ວຍຄວາມຈຳທີ່ສະຫງວນ',
|
||||
powerDefaultLimit: 'ຂີດຈຳກັດພະລັງງານເລີ່ມຕົ້ນ',
|
||||
powerMaxLimit: 'ພະລັງງານສູງສຸດທີ່ຕັ້ງໄດ້',
|
||||
clockEvents: 'ເຫດຜົນທີ່ຈຳກັດຄວາມຖີ່',
|
||||
memoryBandwidth: 'ການໃຊ້ແບນວິດໜ່ວຍຄວາມຈຳ',
|
||||
legacyDevice: 'ບັນທຶກເກົ່າ',
|
||||
legacyHistory: 'ບັນທຶກເຫຼົ່ານີ້ບໍ່ມີ ID ອຸປະກອນຄົງທີ່. ບໍ່ສາມາດຢືນຢັນອຸປະກອນ ແລະ ຄ່າທີ່ຂາດຫາຍໄດ້.',
|
||||
pcieGeneration: 'ລຸ້ນ PCIe (ປັດຈຸບັນ / ສູງສຸດ)',
|
||||
pcieWidth: 'ຄວາມກວ້າງ PCIe (ປັດຈຸບັນ / ສູງສຸດ)',
|
||||
gpu: 'ຕິດຕາມ GPU',
|
||||
gpuHelper: 'ກວດບໍ່ພົບຄຳສັ່ງຈັດການ GPU/XPU/NPU ທີ່ຮອງຮັບ. ກະລຸນາກວດສອບ ແລະ ລອງໃໝ່!',
|
||||
process: 'ຂໍ້ມູນໂປຣເຊສ',
|
||||
@@ -1490,7 +1541,7 @@ const message = {
|
||||
temperature: 'ອຸນຫະພູມ',
|
||||
performanceState: 'ສະຖານະປະສິດທິພາບ',
|
||||
powerUsage: 'ການໃຊ້ພະລັງງານ',
|
||||
memoryUsage: 'ການໃຊ້ໜ່ວຍຄວາມຈຳ',
|
||||
memoryUsage: 'ການໃຊ້ຄວາມຈຸໜ່ວຍຄວາມຈຳ',
|
||||
fanSpeed: 'ຄວາມໄວພັດລົມ',
|
||||
power: 'ພະລັງງານ',
|
||||
powerCurrent: 'ພະລັງງານປັດຈຸບັນ',
|
||||
@@ -6493,6 +6544,15 @@ const message = {
|
||||
snapshot: 'System Snapshot',
|
||||
clamsRulesHelper: 'ງານສະແກນໄວຣັສທີ່ຕ້ອງການແຈ້ງເຕືອນ',
|
||||
cronJobRulesHelper: 'ຕ້ອງຕັ້ງຄ່າງານ Cronjob ປະເພດນີ້',
|
||||
alertTriggerMode: 'ເງື່ອນໄຂແຈ້ງເຕືອນ',
|
||||
alertTriggerFailed: 'ເມື່ອລົ້ມເຫຼວເທົ່ານັ້ນ',
|
||||
alertTriggerSuccess: 'ເມື່ອສຳເລັດເທົ່ານັ້ນ',
|
||||
alertTriggerBoth: 'ເມື່ອສຳເລັດ ຫຼື ລົ້ມເຫຼວ',
|
||||
alertNotificationHelper:
|
||||
'ແຈ້ງເຕືອນຕາມຜົນສຸດທ້າຍ ບໍ່ແມ່ນລະຫວ່າງລອງໃໝ່. ການແຈ້ງເຕືອນສຳເລັດ ແລະ ລົ້ມເຫຼວໃຊ້ຂີດຈຳກັດລາຍວັນ ແລະ ເວລາສົ່ງຮ່ວມກັນ.',
|
||||
notificationTitle: 'ວຽກຕາມກຳນົດເວລາ - {0} 「{1}」 ແຈ້ງເຕືອນການເຮັດວຽກ',
|
||||
notificationSuccessTitle: 'ວຽກຕາມກຳນົດເວລາ - {0} 「{1}」 ສຳເລັດແລ້ວ',
|
||||
notificationRule: '{0}: {1}, ສູງສຸດ {2} ຄັ້ງຕໍ່ມື້',
|
||||
clamsTitle: 'ງານສະແກນໄວຣັສ 「 {0} 」 ກວດພົບໄຟລ໌ອັນຕະລາຍ',
|
||||
cronJobAppTitle: 'ແຈ້ງເຕືອນງານສຳຮອງແອັບ 「 {0} 」 ລົ້ມເຫຼວ',
|
||||
cronJobWebsiteTitle: 'ແຈ້ງເຕືອນງານສຳຮອງເວັບໄຊ 「 {0} 」 ລົ້ມເຫຼວ',
|
||||
|
||||
@@ -1522,6 +1522,59 @@ const message = {
|
||||
extraHeaders: 'Pengepala Tambahan',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'Aktiviti baca/tulis memori',
|
||||
hotspotTemperature: 'Suhu titik panas',
|
||||
fanRPM: 'Kelajuan kipas (RPM)',
|
||||
eccCorrectable: 'Ralat boleh dibetulkan',
|
||||
eccUncorrectable: 'Ralat tidak boleh dibetulkan',
|
||||
eccPending: 'Mod ECC menunggu',
|
||||
engineUtil: 'Penggunaan enjin',
|
||||
cpuUtil: 'CPU · penggunaan',
|
||||
aiCore: 'AICore · penggunaan',
|
||||
aiCPUUtil: 'AICPU · penggunaan',
|
||||
ctrlCPUUtil: 'CtrlCPU · penggunaan',
|
||||
mediaFrequency: 'Media · frekuensi',
|
||||
encoderUtil: 'Pengekod · penggunaan',
|
||||
decoderUtil: 'Penyahkod · penggunaan',
|
||||
jpegUtil: 'JPEG · penggunaan',
|
||||
ofaUtil: 'OFA · penggunaan',
|
||||
mediaUtil: 'Media · penggunaan',
|
||||
computeUtil: 'Pengiraan · penggunaan',
|
||||
copyUtil: 'Salinan · penggunaan',
|
||||
ddrUsage: 'DDR · kapasiti',
|
||||
ddrUsed: 'DDR · digunakan',
|
||||
ddrTotal: 'DDR · jumlah',
|
||||
ddrBandwidth: 'DDR · penggunaan lebar jalur',
|
||||
hbmUsage: 'HBM · kapasiti',
|
||||
hbmUsed: 'HBM · digunakan',
|
||||
hbmTotal: 'HBM · jumlah',
|
||||
hbmBandwidth: 'HBM · penggunaan lebar jalur',
|
||||
hugepagesUsage: 'Halaman besar · bilangan',
|
||||
hugepagesUsed: 'Halaman besar · digunakan',
|
||||
hugepagesTotal: 'Halaman besar · jumlah',
|
||||
|
||||
historyAverage: 'Purata',
|
||||
historyPeak: 'Puncak',
|
||||
historyAggregated:
|
||||
'{count} sampel diagregatkan dalam sela {seconds} saat; proses hanya memaparkan bilangan.',
|
||||
historyRaw: '{count} sampel asal.',
|
||||
historyMetricEmpty: 'Tiada data untuk metrik ini dalam julat masa yang dipilih',
|
||||
historyLoadFailed: 'Gagal memuatkan sejarah. Sila cuba lagi.',
|
||||
collectedAt: 'Masa pengumpulan',
|
||||
processUnavailable: 'Maklumat proses tidak tersedia',
|
||||
architecture: 'Seni bina',
|
||||
memoryFrequency: 'Frekuensi memori',
|
||||
memoryTemperature: 'Suhu memori',
|
||||
memoryReserved: 'Memori terpelihara',
|
||||
powerDefaultLimit: 'Had kuasa lalai',
|
||||
powerMaxLimit: 'Kuasa maksimum boleh ditetapkan',
|
||||
clockEvents: 'Sebab had frekuensi',
|
||||
memoryBandwidth: 'Penggunaan lebar jalur memori',
|
||||
legacyDevice: 'Rekod lama',
|
||||
legacyHistory:
|
||||
'Rekod ini tiada ID peranti yang tetap. Identiti peranti dan nilai yang hilang tidak dapat disahkan.',
|
||||
pcieGeneration: 'Generasi PCIe (semasa / maksimum)',
|
||||
pcieWidth: 'Lebar PCIe (semasa / maksimum)',
|
||||
gpu: 'Pemantauan GPU',
|
||||
gpuHelper: 'Tiada arahan pengurusan GPU/XPU/NPU yang disokong dikesan. Sila periksa dan cuba lagi!',
|
||||
process: 'Maklumat Proses',
|
||||
@@ -1536,7 +1589,7 @@ const message = {
|
||||
temperature: 'Suhu',
|
||||
performanceState: 'Status Prestasi',
|
||||
powerUsage: 'Penggunaan Kuasa',
|
||||
memoryUsage: 'Penggunaan Memori',
|
||||
memoryUsage: 'Penggunaan kapasiti memori',
|
||||
fanSpeed: 'Kelajuan Kipas',
|
||||
power: 'Kuasa',
|
||||
powerCurrent: 'Kuasa Semasa',
|
||||
@@ -6833,6 +6886,15 @@ const message = {
|
||||
snapshot: 'Snapshot Sistem',
|
||||
clamsRulesHelper: 'Tugas imbasan virus yang memerlukan amaran',
|
||||
cronJobRulesHelper: 'Jenis tugas berjadual ini perlu dikonfigurasikan',
|
||||
alertTriggerMode: 'Syarat pemberitahuan',
|
||||
alertTriggerFailed: 'Gagal sahaja',
|
||||
alertTriggerSuccess: 'Berjaya sahaja',
|
||||
alertTriggerBoth: 'Berjaya atau gagal',
|
||||
alertNotificationHelper:
|
||||
'Maklumkan selepas keputusan akhir pelaksanaan, bukan semasa percubaan semula. Pemberitahuan berjaya dan gagal berkongsi had harian dan waktu penghantaran.',
|
||||
notificationTitle: 'Tugas berjadual - {0} 「{1}」 pemberitahuan pelaksanaan',
|
||||
notificationSuccessTitle: 'Tugas berjadual - {0} 「{1}」 berjaya diselesaikan',
|
||||
notificationRule: '{0}: {1}, sehingga {2} pemberitahuan sehari',
|
||||
clamsTitle: 'Tugas imbasan virus 「 {0} 」 mengesan amaran fail dijangkiti',
|
||||
cronJobAppTitle: 'Cronjob - Sandaran Aplikasi 「 {0} 」 Amaran Kegagalan Tugas',
|
||||
cronJobWebsiteTitle: 'Cronjob - Sandaran Laman Web 「 {0} 」 Amaran Kegagalan Tugas',
|
||||
|
||||
@@ -1518,6 +1518,59 @@ const message = {
|
||||
extraHeaders: 'Cabeçalhos extras',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'Atividade de leitura/escrita da memória',
|
||||
hotspotTemperature: 'Temperatura do ponto quente',
|
||||
fanRPM: 'Rotação da ventoinha (RPM)',
|
||||
eccCorrectable: 'Erros corrigíveis',
|
||||
eccUncorrectable: 'Erros incorrigíveis',
|
||||
eccPending: 'Modo ECC pendente',
|
||||
engineUtil: 'Utilização dos motores',
|
||||
cpuUtil: 'CPU · utilização',
|
||||
aiCore: 'AICore · utilização',
|
||||
aiCPUUtil: 'AICPU · utilização',
|
||||
ctrlCPUUtil: 'CtrlCPU · utilização',
|
||||
mediaFrequency: 'Mídia · frequência',
|
||||
encoderUtil: 'Codificador · utilização',
|
||||
decoderUtil: 'Decodificador · utilização',
|
||||
jpegUtil: 'JPEG · utilização',
|
||||
ofaUtil: 'OFA · utilização',
|
||||
mediaUtil: 'Mídia · utilização',
|
||||
computeUtil: 'Computação · utilização',
|
||||
copyUtil: 'Cópia · utilização',
|
||||
ddrUsage: 'DDR · capacidade',
|
||||
ddrUsed: 'DDR · usado',
|
||||
ddrTotal: 'DDR · total',
|
||||
ddrBandwidth: 'DDR · utilização da largura de banda',
|
||||
hbmUsage: 'HBM · capacidade',
|
||||
hbmUsed: 'HBM · usado',
|
||||
hbmTotal: 'HBM · total',
|
||||
hbmBandwidth: 'HBM · utilização da largura de banda',
|
||||
hugepagesUsage: 'Páginas grandes · quantidade',
|
||||
hugepagesUsed: 'Páginas grandes · usado',
|
||||
hugepagesTotal: 'Páginas grandes · total',
|
||||
|
||||
historyAverage: 'Média',
|
||||
historyPeak: 'Pico',
|
||||
historyAggregated:
|
||||
'{count} amostras agregadas em intervalos de {seconds} segundos; processos exibem apenas contagens.',
|
||||
historyRaw: '{count} amostras originais.',
|
||||
historyMetricEmpty: 'Sem dados desta métrica no período selecionado',
|
||||
historyLoadFailed: 'Falha ao carregar o histórico. Tente novamente.',
|
||||
collectedAt: 'Data da coleta',
|
||||
processUnavailable: 'Informações de processos indisponíveis',
|
||||
architecture: 'Arquitetura',
|
||||
memoryFrequency: 'Frequência da memória',
|
||||
memoryTemperature: 'Temperatura da memória',
|
||||
memoryReserved: 'Memória reservada',
|
||||
powerDefaultLimit: 'Limite de potência padrão',
|
||||
powerMaxLimit: 'Potência máxima configurável',
|
||||
clockEvents: 'Motivos de limitação de frequência',
|
||||
memoryBandwidth: 'Uso da largura de banda da memória',
|
||||
legacyDevice: 'Registro antigo',
|
||||
legacyHistory:
|
||||
'Estes registros não têm um ID de dispositivo estável. Não é possível verificar a identidade nem os valores ausentes.',
|
||||
pcieGeneration: 'Geração PCIe (atual / máxima)',
|
||||
pcieWidth: 'Largura PCIe (atual / máxima)',
|
||||
gpu: 'Monitoramento de GPU',
|
||||
gpuHelper:
|
||||
'Nenhum comando de gerenciamento de GPU/XPU/NPU compatível foi detectado. Verifique e tente novamente!',
|
||||
@@ -1533,7 +1586,7 @@ const message = {
|
||||
temperature: 'Temperatura',
|
||||
performanceState: 'Estado de Desempenho',
|
||||
powerUsage: 'Consumo de Energia',
|
||||
memoryUsage: 'Utilização de Memória',
|
||||
memoryUsage: 'Uso da capacidade de memória',
|
||||
fanSpeed: 'Velocidade do Ventilador',
|
||||
power: 'Energia',
|
||||
powerCurrent: 'Energia Atual',
|
||||
@@ -6867,6 +6920,15 @@ const message = {
|
||||
snapshot: 'Instantâneo do Sistema',
|
||||
clamsRulesHelper: 'Tarefas de varredura de vírus que exigem alertas por',
|
||||
cronJobRulesHelper: 'Este tipo de tarefa cron precisa ser configurado',
|
||||
alertTriggerMode: 'Notificar em caso de',
|
||||
alertTriggerFailed: 'Apenas falha',
|
||||
alertTriggerSuccess: 'Apenas sucesso',
|
||||
alertTriggerBoth: 'Sucesso ou falha',
|
||||
alertNotificationHelper:
|
||||
'Notificar após o resultado final, não durante as novas tentativas. As notificações de sucesso e falha compartilham o limite diário e o horário de envio.',
|
||||
notificationTitle: 'Tarefa agendada - {0} 「{1}」 notificação de execução',
|
||||
notificationSuccessTitle: 'Tarefa agendada - {0} 「{1}」 concluída com sucesso',
|
||||
notificationRule: '{0}: {1}, até {2} notificações por dia',
|
||||
clamsTitle: 'Tarefa de Varredura de Vírus 「 {0} 」 detectou arquivo infectado',
|
||||
cronJobAppTitle: 'Tarefa Cron - Falha no Backup do Aplicativo 「 {0} 」',
|
||||
cronJobWebsiteTitle: 'Tarefa Cron - Falha no Backup do Site 「 {0} 」',
|
||||
|
||||
@@ -1511,6 +1511,59 @@ const message = {
|
||||
extraHeaders: 'Дополнительные заголовки',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'Активность чтения/записи памяти',
|
||||
hotspotTemperature: 'Температура горячей точки',
|
||||
fanRPM: 'Скорость вентилятора (об/мин)',
|
||||
eccCorrectable: 'Исправимые ошибки',
|
||||
eccUncorrectable: 'Неисправимые ошибки',
|
||||
eccPending: 'Ожидающий режим ECC',
|
||||
engineUtil: 'Загрузка движков',
|
||||
cpuUtil: 'CPU · загрузка',
|
||||
aiCore: 'AICore · загрузка',
|
||||
aiCPUUtil: 'AICPU · загрузка',
|
||||
ctrlCPUUtil: 'CtrlCPU · загрузка',
|
||||
mediaFrequency: 'Медиа · частота',
|
||||
encoderUtil: 'Кодировщик · загрузка',
|
||||
decoderUtil: 'Декодировщик · загрузка',
|
||||
jpegUtil: 'JPEG · загрузка',
|
||||
ofaUtil: 'OFA · загрузка',
|
||||
mediaUtil: 'Медиа · загрузка',
|
||||
computeUtil: 'Вычисления · загрузка',
|
||||
copyUtil: 'Копирование · загрузка',
|
||||
ddrUsage: 'DDR · объём',
|
||||
ddrUsed: 'DDR · использовано',
|
||||
ddrTotal: 'DDR · всего',
|
||||
ddrBandwidth: 'DDR · использование пропускной способности',
|
||||
hbmUsage: 'HBM · объём',
|
||||
hbmUsed: 'HBM · использовано',
|
||||
hbmTotal: 'HBM · всего',
|
||||
hbmBandwidth: 'HBM · использование пропускной способности',
|
||||
hugepagesUsage: 'Большие страницы · количество',
|
||||
hugepagesUsed: 'Большие страницы · использовано',
|
||||
hugepagesTotal: 'Большие страницы · всего',
|
||||
|
||||
historyAverage: 'Среднее',
|
||||
historyPeak: 'Максимум',
|
||||
historyAggregated:
|
||||
'{count} измерений сгруппированы по {seconds} с; для процессов показано только количество.',
|
||||
historyRaw: 'Исходных измерений: {count}.',
|
||||
historyMetricEmpty: 'Нет данных этой метрики за выбранный период',
|
||||
historyLoadFailed: 'Не удалось загрузить историю. Повторите попытку.',
|
||||
collectedAt: 'Время сбора',
|
||||
processUnavailable: 'Информация о процессах недоступна',
|
||||
architecture: 'Архитектура',
|
||||
memoryFrequency: 'Частота памяти',
|
||||
memoryTemperature: 'Температура памяти',
|
||||
memoryReserved: 'Зарезервированная память',
|
||||
powerDefaultLimit: 'Лимит мощности по умолчанию',
|
||||
powerMaxLimit: 'Максимальная настраиваемая мощность',
|
||||
clockEvents: 'Причины ограничения частоты',
|
||||
memoryBandwidth: 'Использование пропускной способности памяти',
|
||||
legacyDevice: 'Старая запись',
|
||||
legacyHistory:
|
||||
'У этих записей нет постоянного ID устройства. Нельзя проверить принадлежность устройству и отсутствующие значения.',
|
||||
pcieGeneration: 'Поколение PCIe (текущее / максимальное)',
|
||||
pcieWidth: 'Ширина PCIe (текущая / максимальная)',
|
||||
gpu: 'Мониторинг GPU',
|
||||
gpuHelper: 'Поддерживаемая команда управления GPU/XPU/NPU не обнаружена. Проверьте и повторите попытку!',
|
||||
process: 'Информация о Процессе',
|
||||
@@ -1525,7 +1578,7 @@ const message = {
|
||||
temperature: 'Температура',
|
||||
performanceState: 'Состояние Производительности',
|
||||
powerUsage: 'Потребление Мощности',
|
||||
memoryUsage: 'Использование Памяти',
|
||||
memoryUsage: 'Использование объёма памяти',
|
||||
fanSpeed: 'Скорость Вентилятора',
|
||||
power: 'Мощность',
|
||||
powerCurrent: 'Текущая Мощность',
|
||||
@@ -6835,6 +6888,15 @@ const message = {
|
||||
snapshot: 'Системный снимок',
|
||||
clamsRulesHelper: 'Задачи сканирования на вирусы, требующие уведомлений',
|
||||
cronJobRulesHelper: 'Этот тип планируемой задачи требует конфигурации',
|
||||
alertTriggerMode: 'Условие уведомления',
|
||||
alertTriggerFailed: 'Только при ошибке',
|
||||
alertTriggerSuccess: 'Только при успехе',
|
||||
alertTriggerBoth: 'При успехе или ошибке',
|
||||
alertNotificationHelper:
|
||||
'Уведомление отправляется по окончательному результату, а не во время повторных попыток. Уведомления об успехе и ошибке используют общий дневной лимит и часы отправки.',
|
||||
notificationTitle: 'Плановая задача - {0} «{1}»: уведомление о выполнении',
|
||||
notificationSuccessTitle: 'Плановая задача - {0} «{1}» успешно завершена',
|
||||
notificationRule: '{0}: {1}, до {2} уведомлений в день',
|
||||
clamsTitle: 'Задача сканирования на вирусы 「 {0} 」 обнаружила заражённый файл',
|
||||
cronJobAppTitle: 'Задача Cron - ошибка задачи резервного копирования приложения 「 {0} 」',
|
||||
cronJobWebsiteTitle: 'Задача Cron - ошибка задачи резервного копирования сайта 「 {0} 」',
|
||||
|
||||
@@ -1518,6 +1518,58 @@ const message = {
|
||||
extraHeaders: 'Ek Başlıklar',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: 'Bellek okuma/yazma etkinliği',
|
||||
hotspotTemperature: 'Sıcak nokta sıcaklığı',
|
||||
fanRPM: 'Fan hızı (RPM)',
|
||||
eccCorrectable: 'Düzeltilebilir hatalar',
|
||||
eccUncorrectable: 'Düzeltilemeyen hatalar',
|
||||
eccPending: 'Bekleyen ECC modu',
|
||||
engineUtil: 'Motor kullanımı',
|
||||
cpuUtil: 'CPU · kullanım',
|
||||
aiCore: 'AICore · kullanım',
|
||||
aiCPUUtil: 'AICPU · kullanım',
|
||||
ctrlCPUUtil: 'CtrlCPU · kullanım',
|
||||
mediaFrequency: 'Medya · frekans',
|
||||
encoderUtil: 'Kodlayıcı · kullanım',
|
||||
decoderUtil: 'Kod çözücü · kullanım',
|
||||
jpegUtil: 'JPEG · kullanım',
|
||||
ofaUtil: 'OFA · kullanım',
|
||||
mediaUtil: 'Medya · kullanım',
|
||||
computeUtil: 'Hesaplama · kullanım',
|
||||
copyUtil: 'Kopyalama · kullanım',
|
||||
ddrUsage: 'DDR · kapasite',
|
||||
ddrUsed: 'DDR · kullanılan',
|
||||
ddrTotal: 'DDR · toplam',
|
||||
ddrBandwidth: 'DDR · bant genişliği kullanımı',
|
||||
hbmUsage: 'HBM · kapasite',
|
||||
hbmUsed: 'HBM · kullanılan',
|
||||
hbmTotal: 'HBM · toplam',
|
||||
hbmBandwidth: 'HBM · bant genişliği kullanımı',
|
||||
hugepagesUsage: 'Büyük sayfalar · sayı',
|
||||
hugepagesUsed: 'Büyük sayfalar · kullanılan',
|
||||
hugepagesTotal: 'Büyük sayfalar · toplam',
|
||||
|
||||
historyAverage: 'Ortalama',
|
||||
historyPeak: 'En yüksek',
|
||||
historyAggregated:
|
||||
'{count} örnek {seconds} saniyelik aralıklarla toplandı; işlemler için yalnızca sayılar gösterilir.',
|
||||
historyRaw: '{count} ham örnek.',
|
||||
historyMetricEmpty: 'Seçilen zaman aralığında bu metriğe ait veri yok',
|
||||
historyLoadFailed: 'Geçmiş yüklenemedi. Lütfen tekrar deneyin.',
|
||||
collectedAt: 'Toplanma zamanı',
|
||||
processUnavailable: 'İşlem bilgileri kullanılamıyor',
|
||||
architecture: 'Mimari',
|
||||
memoryFrequency: 'Bellek frekansı',
|
||||
memoryTemperature: 'Bellek sıcaklığı',
|
||||
memoryReserved: 'Ayrılmış bellek',
|
||||
powerDefaultLimit: 'Varsayılan güç sınırı',
|
||||
powerMaxLimit: 'Yapılandırılabilir en yüksek güç',
|
||||
clockEvents: 'Saat frekansı sınırlama nedenleri',
|
||||
memoryBandwidth: 'Bellek bant genişliği kullanımı',
|
||||
legacyDevice: 'Eski kayıt',
|
||||
legacyHistory: 'Bu kayıtların sabit cihaz kimliği yoktur. Cihaz kimliği ve eksik değerler doğrulanamaz.',
|
||||
pcieGeneration: 'PCIe nesli (geçerli / en yüksek)',
|
||||
pcieWidth: 'PCIe genişliği (geçerli / en yüksek)',
|
||||
gpu: 'GPU İzleme',
|
||||
gpuHelper: 'Desteklenen bir GPU/XPU/NPU yönetim komutu algılanmadı. Lütfen kontrol edip tekrar deneyin!',
|
||||
process: 'İşlem Bilgisi',
|
||||
@@ -1532,7 +1584,7 @@ const message = {
|
||||
temperature: 'Sıcaklık',
|
||||
performanceState: 'Performans Durumu',
|
||||
powerUsage: 'Güç Tüketimi',
|
||||
memoryUsage: 'Bellek Kullanımı',
|
||||
memoryUsage: 'Bellek kapasitesi kullanımı',
|
||||
fanSpeed: 'Fan Hızı',
|
||||
power: 'Güç',
|
||||
powerCurrent: 'Mevcut Güç',
|
||||
@@ -6841,6 +6893,15 @@ const message = {
|
||||
snapshot: 'Sistem Anlık Görüntüsü',
|
||||
clamsRulesHelper: 'Uyarısı gerektiren virüs tarama görevleri',
|
||||
cronJobRulesHelper: 'Bu tür zamanlanmış görevlerin yapılandırılması gerekir',
|
||||
alertTriggerMode: 'Bildirim koşulu',
|
||||
alertTriggerFailed: 'Yalnızca başarısızlık',
|
||||
alertTriggerSuccess: 'Yalnızca başarı',
|
||||
alertTriggerBoth: 'Başarı veya başarısızlık',
|
||||
alertNotificationHelper:
|
||||
'Yeniden denemeler sırasında değil, son yürütme sonucuna göre bildirim gönderilir. Başarı ve başarısızlık bildirimleri günlük sınırı ve gönderim saatlerini paylaşır.',
|
||||
notificationTitle: 'Zamanlanmış görev - {0} 「{1}」 yürütme bildirimi',
|
||||
notificationSuccessTitle: 'Zamanlanmış görev - {0} 「{1}」 başarıyla tamamlandı',
|
||||
notificationRule: '{0}: {1}, günde en fazla {2} bildirim',
|
||||
clamsTitle: 'Virüs tarama görevi 「 {0} 」 enfekte dosya algıladı uyarısı',
|
||||
cronJobAppTitle: 'Zamanlanmış Görev - Yedekleme Uygulaması 「 {0} 」 Görev Başarısızlık Uyarısı',
|
||||
cronJobWebsiteTitle: 'Zamanlanmış Görev - Yedekleme Web Sitesi 「 {0} 」 Görev Başarısızlık Uyarısı',
|
||||
|
||||
@@ -709,6 +709,11 @@ const message = {
|
||||
keyspaceMisses: '尋找資料庫鍵失敗的次數',
|
||||
hit: '尋找資料庫鍵命中率',
|
||||
latestForkUsec: '最近一次 fork() 操作耗費的微秒數',
|
||||
redisCliEnableTask: '啟用 Redis 遠端終端',
|
||||
redisCliEnabling: '正在啟用 Redis 遠端終端',
|
||||
redisCliEnabled: 'Redis 遠端終端已啟用',
|
||||
redisCliEnableFailed: 'Redis 遠端終端啟用失敗,請查看任務日誌後重試',
|
||||
redisCliTaskSubmitted: '啟用任務已提交,可在任務中心查看進度',
|
||||
redisCliHelper: '未偵測到 redis-cli 服務,請先啟用服務',
|
||||
redisQuickCmd: 'Redis 快速指令',
|
||||
recoverHelper: '即將使用 [{0}] 對資料進行覆蓋,是否繼續?',
|
||||
@@ -1429,6 +1434,57 @@ const message = {
|
||||
extraHeaders: '額外請求標頭',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: '顯示記憶體讀寫活躍率',
|
||||
hotspotTemperature: '熱點溫度',
|
||||
fanRPM: '風扇轉速(RPM)',
|
||||
eccCorrectable: '可修正錯誤數',
|
||||
eccUncorrectable: '不可修正錯誤數',
|
||||
eccPending: '待生效 ECC 模式',
|
||||
engineUtil: '引擎使用率',
|
||||
cpuUtil: 'CPU · 使用率',
|
||||
aiCore: 'AICore · 使用率',
|
||||
aiCPUUtil: 'AICPU · 使用率',
|
||||
ctrlCPUUtil: 'CtrlCPU · 使用率',
|
||||
mediaFrequency: '媒體引擎 · 頻率',
|
||||
encoderUtil: '編碼器 · 使用率',
|
||||
decoderUtil: '解碼器 · 使用率',
|
||||
jpegUtil: 'JPEG · 使用率',
|
||||
ofaUtil: 'OFA · 使用率',
|
||||
mediaUtil: '媒體引擎 · 使用率',
|
||||
computeUtil: '計算引擎 · 使用率',
|
||||
copyUtil: '複製引擎 · 使用率',
|
||||
ddrUsage: 'DDR · 容量',
|
||||
ddrUsed: 'DDR · 已用',
|
||||
ddrTotal: 'DDR · 總量',
|
||||
ddrBandwidth: 'DDR · 頻寬使用率',
|
||||
hbmUsage: 'HBM · 容量',
|
||||
hbmUsed: 'HBM · 已用',
|
||||
hbmTotal: 'HBM · 總量',
|
||||
hbmBandwidth: 'HBM · 頻寬使用率',
|
||||
hugepagesUsage: '大頁 · 數量',
|
||||
hugepagesUsed: '大頁 · 已用',
|
||||
hugepagesTotal: '大頁 · 總量',
|
||||
|
||||
historyAverage: '平均值',
|
||||
historyPeak: '峰值',
|
||||
historyAggregated: '共 {count} 筆取樣,按 {seconds} 秒彙整;程序僅顯示統計數量。',
|
||||
historyRaw: '共 {count} 筆原始取樣。',
|
||||
historyMetricEmpty: '所選時間範圍內沒有此指標的資料',
|
||||
historyLoadFailed: '歷史記錄載入失敗,請重試',
|
||||
collectedAt: '採集時間',
|
||||
processUnavailable: '程序資訊不可用',
|
||||
architecture: '架構',
|
||||
memoryFrequency: '顯存頻率',
|
||||
memoryTemperature: '顯存溫度',
|
||||
memoryReserved: '保留顯存',
|
||||
powerDefaultLimit: '預設功率限制',
|
||||
powerMaxLimit: '最大可設定功率',
|
||||
clockEvents: '頻率限制原因',
|
||||
memoryBandwidth: '顯存頻寬使用率',
|
||||
legacyDevice: '舊記錄',
|
||||
legacyHistory: '這些記錄沒有穩定裝置識別碼,無法確認其裝置身分和缺失值。',
|
||||
pcieGeneration: 'PCIe 世代(目前 / 最大)',
|
||||
pcieWidth: 'PCIe 寬度(目前 / 最大)',
|
||||
gpu: 'GPU 監控',
|
||||
gpuHelper: '目前系統未偵測到支援的 GPU/XPU/NPU 管理指令,請檢查後重試',
|
||||
process: '行程資訊',
|
||||
@@ -1443,7 +1499,7 @@ const message = {
|
||||
temperature: '溫度',
|
||||
performanceState: '效能狀態',
|
||||
powerUsage: '功耗',
|
||||
memoryUsage: '顯存使用率',
|
||||
memoryUsage: '顯示記憶體容量占用率',
|
||||
fanSpeed: '風扇轉速',
|
||||
power: '功率',
|
||||
powerCurrent: '目前功率',
|
||||
@@ -1695,6 +1751,10 @@ const message = {
|
||||
delInsecureHelper: '刪除授信需要重新啟動 docker 服務,是否刪除?',
|
||||
pull: '拉取',
|
||||
path: '路徑',
|
||||
imageImportSource: '匯入方式',
|
||||
imageUploadDrop: '拖曳映像檔案至此處,或點擊上傳',
|
||||
imageUploadHelper: '不限制檔案格式,由 Docker 驗證內容。上傳的檔案會保留,可重試匯入或手動刪除。',
|
||||
imageImportSubmitted: '映像匯入任務已提交,請查看任務日誌',
|
||||
importImage: '匯入映像',
|
||||
imageBuild: '構建映像',
|
||||
buildArgs: '構建參數',
|
||||
@@ -6238,6 +6298,15 @@ const message = {
|
||||
snapshot: '系統快照',
|
||||
clamsRulesHelper: '需要開啟告警的病毒掃描任務',
|
||||
cronJobRulesHelper: '需要設定此類型的計劃任務',
|
||||
alertTriggerMode: '通知條件',
|
||||
alertTriggerFailed: '僅失敗',
|
||||
alertTriggerSuccess: '僅成功',
|
||||
alertTriggerBoth: '成功或失敗',
|
||||
alertNotificationHelper:
|
||||
'任務最終執行完成後按結果通知,重試過程中不發送;成功與失敗共用每日次數及發送時段。',
|
||||
notificationTitle: '計劃任務-{0}「{1}」執行告警',
|
||||
notificationSuccessTitle: '計劃任務-{0}「{1}」執行成功通知',
|
||||
notificationRule: '{0}:{1},每天最多發送 {2} 次',
|
||||
clamsTitle: '病毒掃描「{0}」任務偵測到感染檔案告警',
|
||||
cronJobAppTitle: '計劃任務-備份應用「{0}」任務失敗告警',
|
||||
cronJobWebsiteTitle: '計劃任務-備份網站「{0}」任務失敗告警',
|
||||
|
||||
@@ -722,6 +722,11 @@ const message = {
|
||||
keyspaceMisses: '查找数据库键失败的次数',
|
||||
hit: '查找数据库键命中率',
|
||||
latestForkUsec: '最近一次 fork() 操作耗费的微秒数',
|
||||
redisCliEnableTask: '启用 Redis 远程终端',
|
||||
redisCliEnabling: '正在启用 Redis 远程终端',
|
||||
redisCliEnabled: 'Redis 远程终端已启用',
|
||||
redisCliEnableFailed: 'Redis 远程终端启用失败,请查看任务日志后重试',
|
||||
redisCliTaskSubmitted: '启用任务已提交,可在任务中心查看进度',
|
||||
redisCliHelper: '未检测到 redis-cli 服务,请先启用服务!',
|
||||
redisQuickCmd: 'Redis 快速命令',
|
||||
|
||||
@@ -1442,6 +1447,57 @@ const message = {
|
||||
extraHeaders: '额外请求头',
|
||||
},
|
||||
gpu: {
|
||||
memoryActivity: '显存读写活跃率',
|
||||
hotspotTemperature: '热点温度',
|
||||
fanRPM: '风扇转速(RPM)',
|
||||
eccCorrectable: '可纠正错误数',
|
||||
eccUncorrectable: '不可纠正错误数',
|
||||
eccPending: '待生效 ECC 模式',
|
||||
engineUtil: '引擎使用率',
|
||||
cpuUtil: 'CPU · 使用率',
|
||||
aiCore: 'AICore · 使用率',
|
||||
aiCPUUtil: 'AICPU · 使用率',
|
||||
ctrlCPUUtil: 'CtrlCPU · 使用率',
|
||||
mediaFrequency: '媒体引擎 · 频率',
|
||||
encoderUtil: '编码器 · 使用率',
|
||||
decoderUtil: '解码器 · 使用率',
|
||||
jpegUtil: 'JPEG · 使用率',
|
||||
ofaUtil: 'OFA · 使用率',
|
||||
mediaUtil: '媒体引擎 · 使用率',
|
||||
computeUtil: '计算引擎 · 使用率',
|
||||
copyUtil: '复制引擎 · 使用率',
|
||||
ddrUsage: 'DDR · 容量',
|
||||
ddrUsed: 'DDR · 已用',
|
||||
ddrTotal: 'DDR · 总量',
|
||||
ddrBandwidth: 'DDR · 带宽利用率',
|
||||
hbmUsage: 'HBM · 容量',
|
||||
hbmUsed: 'HBM · 已用',
|
||||
hbmTotal: 'HBM · 总量',
|
||||
hbmBandwidth: 'HBM · 带宽利用率',
|
||||
hugepagesUsage: '大页 · 数量',
|
||||
hugepagesUsed: '大页 · 已用',
|
||||
hugepagesTotal: '大页 · 总量',
|
||||
|
||||
historyAverage: '平均值',
|
||||
historyPeak: '峰值',
|
||||
historyAggregated: '共 {count} 条采样,按 {seconds} 秒聚合;进程仅展示统计数量。',
|
||||
historyRaw: '共 {count} 条原始采样。',
|
||||
historyMetricEmpty: '所选时间范围内没有该指标的数据',
|
||||
historyLoadFailed: '历史记录加载失败,请重试',
|
||||
collectedAt: '采集时间',
|
||||
processUnavailable: '进程信息不可用',
|
||||
architecture: '架构',
|
||||
memoryFrequency: '显存频率',
|
||||
memoryTemperature: '显存温度',
|
||||
memoryReserved: '保留显存',
|
||||
powerDefaultLimit: '默认功率限制',
|
||||
powerMaxLimit: '最大可设置功率',
|
||||
clockEvents: '频率限制原因',
|
||||
memoryBandwidth: '显存带宽利用率',
|
||||
legacyDevice: '旧记录',
|
||||
legacyHistory: '这些记录没有稳定设备标识,无法确认其设备身份和缺失值。',
|
||||
pcieGeneration: 'PCIe 代际(当前 / 最大)',
|
||||
pcieWidth: 'PCIe 宽度(当前 / 最大)',
|
||||
gpu: 'GPU 监控',
|
||||
gpuHelper: '当前系统未检测到支持的 GPU/XPU/NPU 管理指令,请检查后重试!',
|
||||
process: '进程信息',
|
||||
@@ -1457,7 +1513,7 @@ const message = {
|
||||
temperature: '温度',
|
||||
performanceState: '性能状态',
|
||||
powerUsage: '功耗',
|
||||
memoryUsage: '显存使用率',
|
||||
memoryUsage: '显存容量占用率',
|
||||
fanSpeed: '风扇转速',
|
||||
power: '功率',
|
||||
powerCurrent: '当前功率',
|
||||
@@ -1715,6 +1771,10 @@ const message = {
|
||||
delInsecureHelper: '删除授信需要重启 docker 服务,是否删除?',
|
||||
pull: '拉取',
|
||||
path: '路径',
|
||||
imageImportSource: '导入方式',
|
||||
imageUploadDrop: '拖拽镜像文件到此处,或点击上传',
|
||||
imageUploadHelper: '不限制文件格式,由 Docker 校验内容。上传的文件会保留,可重试导入或手动删除。',
|
||||
imageImportSubmitted: '镜像导入任务已提交,请查看任务日志',
|
||||
importImage: '导入镜像',
|
||||
imageBuild: '构建镜像',
|
||||
buildArgs: '构建参数',
|
||||
@@ -6294,6 +6354,15 @@ const message = {
|
||||
snapshot: '系统快照',
|
||||
clamsRulesHelper: '需要开启告警通知的病毒扫描任务',
|
||||
cronJobRulesHelper: '需要配置此类型的计划任务',
|
||||
alertTriggerMode: '通知条件',
|
||||
alertTriggerFailed: '仅失败',
|
||||
alertTriggerSuccess: '仅成功',
|
||||
alertTriggerBoth: '成功或失败',
|
||||
alertNotificationHelper:
|
||||
'任务最终执行完成后按结果通知,重试过程中不发送;成功与失败共用每日次数及发送时段。',
|
||||
notificationTitle: '计划任务-{0}「{1}」执行告警',
|
||||
notificationSuccessTitle: '计划任务-{0}「{1}」执行成功通知',
|
||||
notificationRule: '{0}:{1},每天最多发送 {2} 次',
|
||||
clamsTitle: '病毒扫描「 {0} 」任务检测到感染文件告警',
|
||||
cronJobAppTitle: '计划任务-备份应用「 {0} 」任务失败告警',
|
||||
cronJobWebsiteTitle: '计划任务-备份网站「 {0} 」任务失败告警',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ref, reactive, toRef } from 'vue';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { checkStreamAuth } from '@/utils/stream-auth';
|
||||
|
||||
@@ -46,6 +47,17 @@ export const ProcessStore = defineStore('ProcessStore', () => {
|
||||
port: undefined,
|
||||
});
|
||||
|
||||
useSearchPersistence('host/process/process', {
|
||||
pid: toRef(psSearch, 'pid'),
|
||||
name: toRef(psSearch, 'name'),
|
||||
username: toRef(psSearch, 'username'),
|
||||
});
|
||||
useSearchPersistence('host/process/network', {
|
||||
processID: toRef(netSearch, 'processID'),
|
||||
processName: toRef(netSearch, 'processName'),
|
||||
port: toRef(netSearch, 'port'),
|
||||
});
|
||||
|
||||
let pendingRequestType: 'ps' | 'net' | null = null;
|
||||
|
||||
let queuedRequestType: 'ps' | 'net' | null = null;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
export type CronjobAlertTriggerMode = 'failed' | 'success' | 'both';
|
||||
|
||||
export const cronjobAlertTypes = [
|
||||
'shell',
|
||||
'app',
|
||||
'website',
|
||||
'database',
|
||||
'directory',
|
||||
'log',
|
||||
'snapshot',
|
||||
'curl',
|
||||
'cutWebsiteLog',
|
||||
'clean',
|
||||
'ntp',
|
||||
'syncIpGroup',
|
||||
'cleanLog',
|
||||
];
|
||||
|
||||
export const cronjobAlertModes = [
|
||||
{ value: 'failed', label: 'xpack.alert.alertTriggerFailed' },
|
||||
{ value: 'success', label: 'xpack.alert.alertTriggerSuccess' },
|
||||
{ value: 'both', label: 'xpack.alert.alertTriggerBoth' },
|
||||
] as const;
|
||||
|
||||
export const normalizeCronjobAlertMode = (value: unknown): CronjobAlertTriggerMode => {
|
||||
return value === 'success' || value === 'both' ? value : 'failed';
|
||||
};
|
||||
|
||||
const parseAdvancedParams = (value?: string): Record<string, unknown> => {
|
||||
try {
|
||||
const params = JSON.parse(value || '{}');
|
||||
return params && typeof params === 'object' && !Array.isArray(params) ? params : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const getCronjobAlertMode = (advancedParams?: string): CronjobAlertTriggerMode => {
|
||||
return normalizeCronjobAlertMode(parseAdvancedParams(advancedParams).alertTriggerMode);
|
||||
};
|
||||
|
||||
export const setCronjobAlertMode = (advancedParams: string | undefined, mode: CronjobAlertTriggerMode): string => {
|
||||
return JSON.stringify({ ...parseAdvancedParams(advancedParams), alertTriggerMode: mode });
|
||||
};
|
||||
|
||||
export const getCronjobAlertModeLabel = (mode: unknown): string => {
|
||||
return cronjobAlertModes.find((item) => item.value === normalizeCronjobAlertMode(mode))!.label;
|
||||
};
|
||||
|
||||
export const getCronjobAlertTitle = (
|
||||
row: { type: string; subType?: string; taskName?: string; title: string },
|
||||
renderTitle: (type: string, taskName: string) => string,
|
||||
): string => {
|
||||
const type = row.type === 'cronJob' ? row.subType : row.type;
|
||||
if (type && cronjobAlertTypes.includes(type) && typeof row.taskName === 'string' && row.taskName.length > 0) {
|
||||
return renderTitle(type, row.taskName);
|
||||
}
|
||||
return row.title;
|
||||
};
|
||||
|
||||
export const getCronjobAlertResult = (params: unknown): 'success' | 'failed' => {
|
||||
if (typeof params === 'string') {
|
||||
try {
|
||||
params = JSON.parse(params);
|
||||
} catch {
|
||||
return 'failed';
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(params)) return 'failed';
|
||||
const result = params.find((item) => item?.index === 'result');
|
||||
if (result) return result.value === 'success' ? 'success' : 'failed';
|
||||
const numberedResult = params.find((item) => item?.index === '2' && item?.key === 'result');
|
||||
return numberedResult?.value === '成功' ? 'success' : 'failed';
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { watch, type Ref } from 'vue';
|
||||
|
||||
type SearchValue = string | number | undefined;
|
||||
type SearchFields = Record<string, Ref<SearchValue>>;
|
||||
|
||||
export const bindSearchPersistence = (key: () => string | undefined, fields: SearchFields) => {
|
||||
const defaults = Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value]));
|
||||
let initialized = false;
|
||||
let restoring = false;
|
||||
let activeKey: string | undefined;
|
||||
|
||||
const stopRestore = watch(
|
||||
key,
|
||||
(value) => {
|
||||
activeKey = value ? `1panel:search:v1:${value}` : undefined;
|
||||
let saved: Record<string, unknown> = {};
|
||||
try {
|
||||
const parsed = activeKey ? JSON.parse(localStorage.getItem(activeKey) || '{}') : {};
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) saved = parsed;
|
||||
} catch {
|
||||
// Invalid or unavailable storage must not prevent a page from loading.
|
||||
}
|
||||
restoring = true;
|
||||
for (const [name, field] of Object.entries(fields)) {
|
||||
const value = saved[name];
|
||||
const initial = defaults[name];
|
||||
const fallback = initialized ? (typeof initial === 'string' ? '' : undefined) : initial;
|
||||
const valid =
|
||||
(typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))) &&
|
||||
(fallback === undefined || typeof value === typeof fallback);
|
||||
field.value =
|
||||
value === null
|
||||
? typeof initial === 'string'
|
||||
? ''
|
||||
: undefined
|
||||
: valid
|
||||
? (value as SearchValue)
|
||||
: fallback;
|
||||
}
|
||||
restoring = false;
|
||||
initialized = true;
|
||||
},
|
||||
{ immediate: true, flush: 'sync' },
|
||||
);
|
||||
|
||||
const stopSave = watch(
|
||||
() => Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value ?? null])),
|
||||
(value) => {
|
||||
if (restoring || !activeKey) return;
|
||||
try {
|
||||
localStorage.setItem(activeKey, JSON.stringify(value));
|
||||
} catch {
|
||||
// Search remains usable when browser storage is disabled or full.
|
||||
}
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
|
||||
return () => {
|
||||
stopRestore();
|
||||
stopSave();
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { watch, type Ref } from 'vue';
|
||||
|
||||
type SearchFields = Record<string, Ref<string | number | undefined>>;
|
||||
|
||||
/** Search terms belong to the current tab visit, not browser storage or cached pages. */
|
||||
export const bindSearchReset = (context: () => unknown, fields: SearchFields) => {
|
||||
const emptyValues = Object.fromEntries(
|
||||
Object.entries(fields).map(([name, field]) => [name, typeof field.value === 'string' ? '' : undefined]),
|
||||
);
|
||||
return watch(
|
||||
() => JSON.stringify(context()),
|
||||
() => {
|
||||
for (const [name, field] of Object.entries(fields)) field.value = emptyValues[name];
|
||||
},
|
||||
{ immediate: true, flush: 'sync' },
|
||||
);
|
||||
};
|
||||
@@ -24,6 +24,7 @@ const formatSkillInstallTask = () => {
|
||||
};
|
||||
|
||||
const taskTextMap: Record<string, () => string> = {
|
||||
RedisCliEnable: () => i18n.global.t('database.redisCliEnableTask'),
|
||||
BatchInstallAgent: () => formatBatchAgentTask('Install'),
|
||||
DispatchAgentInstallTasks: () => formatDispatchAgentTask('Install'),
|
||||
BatchUpgradeAgent: () => formatBatchAgentTask('Upgrade'),
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
@@ -192,6 +193,11 @@ const searching = ref(false);
|
||||
const operating = ref('');
|
||||
const keyword = ref('');
|
||||
const marketKeyword = ref('');
|
||||
useSearchPersistence(
|
||||
'ai/agents/agent/config/tabs/plugins',
|
||||
{ keyword, marketKeyword },
|
||||
() => agentId.value || undefined,
|
||||
);
|
||||
const origin = ref('');
|
||||
const status = ref('');
|
||||
const page = ref(1);
|
||||
@@ -299,8 +305,6 @@ async function operate(plugin: AI.AgentPluginItem, action: PluginOperate) {
|
||||
async function load(id: number) {
|
||||
agentId.value = id;
|
||||
mode.value = 'installed';
|
||||
keyword.value = '';
|
||||
marketKeyword.value = '';
|
||||
origin.value = '';
|
||||
status.value = '';
|
||||
page.value = 1;
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -168,6 +169,11 @@ const getDefaultMarketSource = (): HermesSkillMarketSource => {
|
||||
const marketSource = ref<HermesSkillMarketSource>(getDefaultMarketSource());
|
||||
const marketSearched = ref(false);
|
||||
const agentId = ref(0);
|
||||
useSearchPersistence(
|
||||
'ai/agents/agent/config/tabs/skills/hermes',
|
||||
{ installedKeyword, marketKeyword },
|
||||
() => agentId.value || undefined,
|
||||
);
|
||||
const installedSkills = ref<AI.AgentSkillItem[]>([]);
|
||||
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
||||
const installingSkill = ref('');
|
||||
@@ -315,8 +321,6 @@ const handleModeChange = async (value: SkillViewMode) => {
|
||||
const load = async (id: number) => {
|
||||
agentId.value = id;
|
||||
mode.value = 'market';
|
||||
installedKeyword.value = '';
|
||||
marketKeyword.value = '';
|
||||
marketSource.value = getDefaultMarketSource();
|
||||
marketSearched.value = false;
|
||||
installedSkills.value = [];
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -180,6 +181,11 @@ const getDefaultMarketSource = (): SkillMarketSource => {
|
||||
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
|
||||
const marketSearched = ref(false);
|
||||
const agentId = ref(0);
|
||||
useSearchPersistence(
|
||||
'ai/agents/agent/config/tabs/skills/openclaw',
|
||||
{ installedKeyword, marketKeyword },
|
||||
() => agentId.value || undefined,
|
||||
);
|
||||
const skills = ref<AI.AgentSkillItem[]>([]);
|
||||
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
||||
const updatingSkill = ref('');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user