ref: update alert logging methods and improve alert configuration handling (#12955)

This commit is contained in:
2026-06-08 21:47:47 +08:00
committed by GitHub
parent 338301907a
commit a2e6e7e879
10 changed files with 140 additions and 131 deletions
+3 -3
View File
@@ -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 {
+10 -6
View File
@@ -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)
}
}
+2 -35
View File
@@ -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 ""
}
}
+1 -1
View File
@@ -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{
+1
View File
@@ -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 {
+91 -24
View File
@@ -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))
+4 -52
View File
@@ -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) {
@@ -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 (
@@ -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();
});
</script>
@@ -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<Alert.AlertConfigInfo>({
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<FormInstance>();
const noticeValue: ComputedRef<string[]> = 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;