From a2e6e7e879a707e13e7c1779ab277ee4750aba76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=98=AD?= <81747598+lan-yonghui@users.noreply.github.com> Date: Mon, 8 Jun 2026 21:47:47 +0800 Subject: [PATCH] ref: update alert logging methods and improve alert configuration handling (#12955) --- agent/app/api/v2/alert.go | 6 +- agent/app/repo/alert.go | 16 ++- agent/app/service/alert.go | 37 +----- agent/app/service/clam.go | 2 +- agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 115 ++++++++++++++---- agent/utils/xpack/helper/alert.go | 56 +-------- .../src/components/complex-table/index.vue | 8 ++ .../src/views/setting/alert/log/index.vue | 6 +- .../alert/setting/time-range/index.vue | 24 ++-- 10 files changed, 140 insertions(+), 131 deletions(-) diff --git a/agent/app/api/v2/alert.go b/agent/app/api/v2/alert.go index 9bdaf932e..a8000f296 100644 --- a/agent/app/api/v2/alert.go +++ b/agent/app/api/v2/alert.go @@ -53,7 +53,7 @@ func (b *BaseApi) GetAlerts(c *gin.Context) { // @Security ApiKeyAuth // @Security Timestamp // @Router /alert [post] -// @x-panel-log {"bodyKeys":["type","method"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建告警任务 [type][method]","formatEN":"create alert [type][method]"} +// @x-panel-log {"bodyKeys":["type","method"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建告警任务 [title]","formatEN":"create alert [title]"} func (b *BaseApi) CreateAlert(c *gin.Context) { var req dto.AlertCreate if err := helper.CheckBindAndValidate(&req, c); err != nil { @@ -97,7 +97,7 @@ func (b *BaseApi) DeleteAlert(c *gin.Context) { // @Security ApiKeyAuth // @Security Timestamp // @Router /alert/update [post] -// @x-panel-log {"bodyKeys":["id","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警任务 [id][type]","formatEN":"update alert [id][type]"} +// @x-panel-log {"bodyKeys":["id","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警任务 [id][title]","formatEN":"update alert [id][title]"} func (b *BaseApi) UpdateAlert(c *gin.Context) { var req dto.AlertUpdate if err := helper.CheckBindAndValidate(&req, c); err != nil { @@ -287,7 +287,7 @@ func (b *BaseApi) PageAlertConfig(c *gin.Context) { // @Security ApiKeyAuth // @Security Timestamp // @Router /alert/config/update [post] -// @x-panel-log {"bodyKeys":["type","title"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置 [type][title]","formatEN":"update alert config [type][title]"} +// @x-panel-log {"bodyKeys":["type","title"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置 [id][type]","formatEN":"update alert config [id][type]"} func (b *BaseApi) UpdateAlertConfig(c *gin.Context) { var req dto.AlertConfigUpdate if err := helper.CheckBindAndValidate(&req, c); err != nil { diff --git a/agent/app/repo/alert.go b/agent/app/repo/alert.go index a39de6446..e62d1fa02 100644 --- a/agent/app/repo/alert.go +++ b/agent/app/repo/alert.go @@ -21,8 +21,8 @@ type IAlertRepo interface { WithByCreateAt(date *date.Date) DBOption WithByLicenseId(licenseId string) DBOption WithByRecordId(recordId uint) DBOption - WithByMethod(method string) DBOption - WithByMethodConfigID(id uint) DBOption + WithByAlertMethodContainsConfigID(id uint) DBOption + WithByMethodConfigIDs(ids []uint) DBOption Create(alert *model.Alert) error Get(opts ...DBOption) (model.Alert, error) @@ -107,16 +107,20 @@ func (a *AlertRepo) WithByRecordId(recordId uint) DBOption { } } -func (a *AlertRepo) WithByMethod(method string) DBOption { +func (a *AlertRepo) WithByAlertMethodContainsConfigID(id uint) DBOption { + method := strconv.Itoa(int(id)) return func(g *gorm.DB) *gorm.DB { return g.Where("(method = ? OR method LIKE ? OR method LIKE ? OR method LIKE ?)", method, method+",%", "%,"+method, "%,"+method+",%") } } -func (a *AlertRepo) WithByMethodConfigID(id uint) DBOption { - method := strconv.Itoa(int(id)) +func (a *AlertRepo) WithByMethodConfigIDs(ids []uint) DBOption { return func(g *gorm.DB) *gorm.DB { - return g.Where("(method = ? OR method LIKE ? OR method LIKE ? OR method LIKE ?)", method, method+",%", "%,"+method, "%,"+method+",%") + methods := make([]string, 0, len(ids)) + for _, id := range ids { + methods = append(methods, strconv.Itoa(int(id))) + } + return g.Where("method IN ?", methods) } } diff --git a/agent/app/service/alert.go b/agent/app/service/alert.go index 2b3096683..18c3de59c 100644 --- a/agent/app/service/alert.go +++ b/agent/app/service/alert.go @@ -561,28 +561,14 @@ func alertConfigDisplayName(configType, configData string) string { } func (a AlertService) DeleteAlertConfig(id uint) error { - config, err := alertRepo.GetConfigById(id) + _, err := alertRepo.GetConfigById(id) if err != nil { return err } - usedAlerts, err := alertRepo.List(alertRepo.WithByMethodConfigID(id)) + usedAlerts, err := alertRepo.List(alertRepo.WithByAlertMethodContainsConfigID(id)) if err != nil { return err } - if config.Type == constant.SMS { - legacyAlerts, err := alertRepo.List(alertRepo.WithByMethod(constant.SMS)) - if err != nil { - return err - } - usedAlerts = append(usedAlerts, legacyAlerts...) - } - if legacyMethod := legacyAlertMethodByConfigType(config.Type); legacyMethod != "" { - legacyAlerts, err := alertRepo.List(alertRepo.WithByMethod(legacyMethod)) - if err != nil { - return err - } - usedAlerts = append(usedAlerts, legacyAlerts...) - } if len(usedAlerts) > 0 { return buserr.New("ErrAlertConfigInUse") } @@ -670,22 +656,3 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator return nil } - -func legacyAlertMethodByConfigType(configType string) string { - switch configType { - case constant.Email: - return "mail" - case constant.SMS: - return constant.SMS - case constant.Bark: - return constant.Bark - case constant.WeCom: - return constant.WeCom - case constant.DingTalk: - return constant.DingTalk - case constant.FeiShu: - return constant.FeiShu - default: - return "" - } -} diff --git a/agent/app/service/clam.go b/agent/app/service/clam.go index 66deeea4e..444e25562 100644 --- a/agent/app/service/clam.go +++ b/agent/app/service/clam.go @@ -494,7 +494,7 @@ func (c *ClamService) loadConfigPath(confType string) string { func handleAlert(infectedFiles, clamName string, clamId uint) { itemInfected, _ := strconv.Atoi(strings.TrimSpace(infectedFiles)) - if itemInfected < 0 { + if itemInfected <= 0 { return } pushAlert := dto.PushAlert{ diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 305da3181..d712678da 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -107,6 +107,7 @@ func InitTaskDB() { func InitAlertDB() { m := gormigrate.New(global.AlertDB, gormigrate.DefaultOptions, []*gormigrate.Migration{ migrations.MigrateAlertMethodConfigIDs, + migrations.MigrateAlertLogTaskMethodConfigIDs, migrations.AddAlertAuditUser, }) if err := m.Migrate(); err != nil { diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 8119a9817..2bd770e45 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -487,7 +487,23 @@ var MigrateAlertMethodConfigIDs = &gormigrate.Migration{ if err := global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertLog{}, &model.AlertTask{}, &model.AlertConfig{}); err != nil { return err } - if err := migrateAlertMethodConfigIDs(global.AlertDB); err != nil { + if err := migrateAlertMethodConfigIDs(tx); err != nil { + return err + } + return nil + }, +} + +var MigrateAlertLogTaskMethodConfigIDs = &gormigrate.Migration{ + ID: "20260608-migrate-alert-log-task-method-config-ids", + Migrate: func(tx *gorm.DB) error { + if err := global.AlertDB.AutoMigrate(&model.AlertLog{}, &model.AlertTask{}, &model.AlertConfig{}); err != nil { + return err + } + if err := migrateAlertMethodRecords(tx, &model.AlertLog{}); err != nil { + return err + } + if err := migrateAlertMethodRecords(tx, &model.AlertTask{}); err != nil { return err } return nil @@ -506,28 +522,9 @@ func migrateAlertMethodConfigIDs(tx *gorm.DB) error { return err } - typeMap := map[string]string{ - "mail": constant.Email, - constant.Email: constant.Email, - constant.SMS: constant.SMS, - constant.Bark: constant.Bark, - constant.WeCom: constant.WeCom, - constant.DingTalk: constant.DingTalk, - constant.FeiShu: constant.FeiShu, - } - configIDs := map[string]string{} - for _, configType := range typeMap { - if _, ok := configIDs[configType]; ok { - continue - } - var config model.AlertConfig - if err := tx.Where("type = ?", configType).Order("id ASC").First(&config).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - continue - } - return err - } - configIDs[configType] = strconv.Itoa(int(config.ID)) + configIDs, err := loadAlertConfigIDs(tx) + if err != nil { + return err } var alerts []model.Alert @@ -535,7 +532,7 @@ func migrateAlertMethodConfigIDs(tx *gorm.DB) error { return err } for _, alert := range alerts { - method := migrateAlertMethodValue(alert.Method, typeMap, configIDs) + method := migrateAlertMethodValue(alert.Method, alertLegacyMethodTypeMap(), configIDs) if method == alert.Method { continue } @@ -546,6 +543,76 @@ func migrateAlertMethodConfigIDs(tx *gorm.DB) error { return nil } +func migrateAlertMethodRecords(tx *gorm.DB, modelValue interface{}) error { + configIDs, err := loadAlertConfigIDs(tx) + if err != nil { + return err + } + + switch modelValue.(type) { + case *model.AlertLog: + var logs []model.AlertLog + if err := tx.Find(&logs).Error; err != nil { + return err + } + for _, item := range logs { + method := migrateAlertMethodValue(item.Method, alertLegacyMethodTypeMap(), configIDs) + if method == item.Method { + continue + } + if err := tx.Model(&model.AlertLog{}).Where("id = ?", item.ID).Update("method", method).Error; err != nil { + return err + } + } + case *model.AlertTask: + var tasks []model.AlertTask + if err := tx.Find(&tasks).Error; err != nil { + return err + } + for _, item := range tasks { + method := migrateAlertMethodValue(item.Method, alertLegacyMethodTypeMap(), configIDs) + if method == item.Method { + continue + } + if err := tx.Model(&model.AlertTask{}).Where("id = ?", item.ID).Update("method", method).Error; err != nil { + return err + } + } + } + return nil +} + +func loadAlertConfigIDs(tx *gorm.DB) (map[string]string, error) { + configIDs := map[string]string{} + for _, configType := range alertLegacyMethodTypeMap() { + if _, ok := configIDs[configType]; ok { + continue + } + var config model.AlertConfig + if err := tx.Where("type = ?", configType).Order("id ASC").First(&config).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + return nil, err + } + configIDs[configType] = strconv.Itoa(int(config.ID)) + } + return configIDs, nil +} + +func alertLegacyMethodTypeMap() map[string]string { + return map[string]string{ + "mail": constant.Email, + constant.Email: constant.Email, + constant.SMS: constant.SMS, + constant.Bark: constant.Bark, + constant.WeChat: constant.WeCom, + constant.WeCom: constant.WeCom, + constant.DingTalk: constant.DingTalk, + constant.FeiShu: constant.FeiShu, + } +} + func migrateAlertMethodValue(method string, typeMap map[string]string, configIDs map[string]string) string { items := strings.Split(method, ",") next := make([]string, 0, len(items)) diff --git a/agent/utils/xpack/helper/alert.go b/agent/utils/xpack/helper/alert.go index 1d4f75813..b35693c80 100644 --- a/agent/utils/xpack/helper/alert.go +++ b/agent/utils/xpack/helper/alert.go @@ -1,15 +1,10 @@ package helper import ( - "encoding/json" - "fmt" "net/http" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" - "github.com/1Panel-dev/1Panel/agent/constant" - alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert" - "github.com/1Panel-dev/1Panel/agent/utils/bark" "github.com/1Panel-dev/1Panel/agent/utils/xpack/providers" ) @@ -20,62 +15,19 @@ func NewIAlertProvider() providers.AlertProvider { } func (a *alertHelper) CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, config model.AlertConfig, method string) error { - params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param)) - create.AlertRule = alertUtil.ProcessAlertRule(alert) - create.AlertDetail = alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, method) - if create.Status == "" { - create.Status = constant.AlertSuccess - } - return alertUtil.SaveAlertLog(create, &model.AlertLog{}) + return nil } func (a *alertHelper) CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, config model.AlertConfig, method string) error { - create.AlertRule = alertUtil.ProcessAlertRule(info) - if create.AlertDetail == "" { - create.AlertDetail = alertUtil.ProcessAlertDetail(info, project, params, method) - } - if create.Status == "" { - create.Status = constant.AlertSuccess - } - return alertUtil.SaveAlertLog(create, &model.AlertLog{}) + return nil } func (a *alertHelper) CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, config model.AlertConfig, transport *http.Transport, agentInfo *dto.AgentInfo) error { - params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param)) - create.AlertRule = alertUtil.ProcessAlertRule(alert) - create.AlertDetail = alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, config.Type) - return a.CreateWebhookAlertLog(alertType, alert, create, pushAlert.TaskName, params, config, transport, agentInfo) + return nil } func (a *alertHelper) CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, config model.AlertConfig, transport *http.Transport, agentInfo *dto.AgentInfo) error { - var webhookInfo dto.AlertWebhookConfig - if err := json.Unmarshal([]byte(config.Config), &webhookInfo); err != nil { - create.Message = err.Error() - create.Status = constant.AlertError - return alertUtil.SaveAlertLog(create, &model.AlertLog{}) - } - if webhookInfo.Url == "" { - create.Message = "webhook url is required" - create.Status = constant.AlertError - return alertUtil.SaveAlertLog(create, &model.AlertLog{}) - } - create.AlertRule = alertUtil.ProcessAlertRule(info) - if create.AlertDetail == "" { - create.AlertDetail = alertUtil.ProcessAlertDetail(info, project, params, config.Type) - } - content := alertUtil.GetSendContent(info.Type, params, agentInfo) - if content == "" { - content = fmt.Sprintf("%s: %s", info.Type, info.Title) - } - if err := bark.SendMessage(webhookInfo.Url, "1Panel Alert", content, transport); err != nil { - create.Message = err.Error() - create.Status = constant.AlertError - return alertUtil.SaveAlertLog(create, &model.AlertLog{}) - } - if create.Status == "" { - create.Status = constant.AlertSuccess - } - return alertUtil.SaveAlertLog(create, &model.AlertLog{}) + return nil } func (a *alertHelper) GetLicenseErrorAlert() (uint, error) { diff --git a/frontend/src/components/complex-table/index.vue b/frontend/src/components/complex-table/index.vue index 778064899..7e849cf81 100644 --- a/frontend/src/components/complex-table/index.vue +++ b/frontend/src/components/complex-table/index.vue @@ -319,6 +319,11 @@ function clearTextSelection() { } } +function hasActiveTextSelection() { + const selection = window.getSelection?.(); + return !!selection && !selection.isCollapsed && selection.toString().trim().length > 0; +} + const updatePaginationWidth = () => { paginationWidth.value = paginationRef.value?.clientWidth || 0; }; @@ -373,6 +378,9 @@ function handleRowClick(row: any, column: any, event: any) { if (!isRowSelectable(row)) return; const target = event.target as HTMLElement; + if (hasActiveTextSelection() && !event.shiftKey) { + return; + } if (target.closest('.el-checkbox')) return; if ( diff --git a/frontend/src/views/setting/alert/log/index.vue b/frontend/src/views/setting/alert/log/index.vue index 5d1c4af67..a91026df2 100644 --- a/frontend/src/views/setting/alert/log/index.vue +++ b/frontend/src/views/setting/alert/log/index.vue @@ -293,7 +293,6 @@ const search = async () => { status: req.status, }; try { - await loadConfigMap(); const res = await SearchAlertLogs(params); data.value = res.data.items || []; paginationConfig.total = res.data.total || 0; @@ -309,6 +308,7 @@ const syncAll = async () => { cancelButtonText: t('commons.button.cancel'), }).then(async () => { await syncAllAlert(); + await search(); MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); }); }; @@ -319,7 +319,6 @@ const syncAllAlert = async () => { } else { await SyncAlertAll(); } - await search(); }; const onClean = async () => { @@ -353,13 +352,14 @@ const searchAlertInfo = async () => { loading.value = false; } } - await search(); }; onMounted(async () => { + await loadConfigMap(); await searchAlertInfo(); if (isProductPro.value && !isIntl.value) { await syncAllAlert(); } + await search(); }); diff --git a/frontend/src/views/setting/alert/setting/time-range/index.vue b/frontend/src/views/setting/alert/setting/time-range/index.vue index 66fa33d46..5ac0d7e18 100644 --- a/frontend/src/views/setting/alert/setting/time-range/index.vue +++ b/frontend/src/views/setting/alert/setting/time-range/index.vue @@ -70,8 +70,10 @@ import { MsgError, MsgSuccess } from '@/utils/message'; import { FormInstance } from 'element-plus'; import { UpdateAlertConfig } from '@/api/modules/alert'; import { Alert } from '@/api/interface/alert'; +import { useGlobalStore } from '@/composables/useGlobalStore'; const emit = defineEmits<{ (e: 'search'): void }>(); +const { isProductPro, isIntl, isEE } = useGlobalStore(); interface Option { key: string; @@ -123,7 +125,7 @@ const config = ref({ status: '', config: '', }); -const resourceValue = ref([ +const defaultResourceValue = [ 'clams', 'cronJob', 'cpu', @@ -134,17 +136,21 @@ const resourceValue = ref([ 'licenseException', 'panelLogin', 'sshLogin', -]); +]; const noticeDefaultTime: [Date, Date] = [new Date(0, 0, 1, 8, 0, 0), new Date(0, 0, 1, 23, 59, 59)]; const resourceDefaultTime: [Date, Date] = [new Date(0, 0, 1, 0, 0, 0), new Date(0, 0, 1, 23, 59, 59)]; const noticeTimeRange = ref(noticeDefaultTime); const resourceTimeRange = ref(resourceDefaultTime); const generateData = (): Option[] => { const data: Option[] = []; - data.push({ key: 'panelPwdEndTime', label: i18n.global.t('xpack.alert.panelPwdEndTime'), disabled: false }); + if (!isEE.value) { + data.push({ key: 'panelPwdEndTime', label: i18n.global.t('xpack.alert.panelPwdEndTime'), disabled: false }); + } data.push({ key: 'panelLogin', label: i18n.global.t('xpack.alert.panelLogin'), disabled: false }); data.push({ key: 'sshLogin', label: i18n.global.t('xpack.alert.sshLogin'), disabled: false }); - data.push({ key: 'licenseException', label: i18n.global.t('xpack.alert.licenseException'), disabled: false }); + if (isProductPro.value && !isIntl.value && !isEE.value) { + data.push({ key: 'licenseException', label: i18n.global.t('xpack.alert.licenseException'), disabled: false }); + } data.push({ key: 'nodeException', label: i18n.global.t('xpack.alert.nodeException'), disabled: false }); data.push({ key: 'ssl', label: i18n.global.t('xpack.alert.ssl'), disabled: false }); data.push({ key: 'siteEndTime', label: i18n.global.t('xpack.alert.siteEndTime'), disabled: false }); @@ -154,11 +160,15 @@ const generateData = (): Option[] => { data.push({ key: 'load', label: i18n.global.t('xpack.alert.load'), disabled: false }); data.push({ key: 'clams', label: i18n.global.t('xpack.alert.clams'), disabled: false }); data.push({ key: 'cronJob', label: i18n.global.t('xpack.alert.cronjob'), disabled: false }); - data.push({ key: 'panelUpdate', label: i18n.global.t('xpack.alert.panelUpdate'), disabled: false }); + if (!isEE.value) { + data.push({ key: 'panelUpdate', label: i18n.global.t('xpack.alert.panelUpdate'), disabled: false }); + } return data; }; -const data = ref(generateData()); +const data = computed(() => generateData()); +const dataKeySet = computed(() => new Set(data.value.map((item) => item.key))); +const resourceValue = ref(defaultResourceValue.filter((item) => dataKeySet.value.has(item))); const formRef = ref(); const noticeValue: ComputedRef = computed(() => { return data.value.filter((item) => !resourceValue.value.includes(item.key)).map((item) => item.key); @@ -168,7 +178,7 @@ const acceptParams = (params: DialogProps): void => { if (typeof params.sendTimeRange === 'object' && params.sendTimeRange !== null) { noticeTimeRange.value = parseTimeRange(params.sendTimeRange.noticeAlert.sendTimeRange); resourceTimeRange.value = parseTimeRange(params.sendTimeRange.resourceAlert.sendTimeRange); - resourceValue.value = params.sendTimeRange.resourceAlert.type; + resourceValue.value = params.sendTimeRange.resourceAlert.type.filter((item) => dataKeySet.value.has(item)); } isOffline.value = params.isOffline; id.value = params.id;