mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
feat: Support adding multiple alert methods (#12922)
* feat: Support adding multiple alert methods * feat: Support adding multiple alert methods
This commit is contained in:
@@ -2,11 +2,16 @@ package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const defaultAuditUser = "system"
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Page alert
|
||||
// @Accept json
|
||||
@@ -54,7 +59,7 @@ func (b *BaseApi) CreateAlert(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := alertService.CreateAlert(req)
|
||||
err := alertService.CreateAlert(req, loadAuditUser(c))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -98,7 +103,7 @@ func (b *BaseApi) UpdateAlert(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlert(req); err != nil {
|
||||
if err := alertService.UpdateAlert(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -246,6 +251,30 @@ func (b *BaseApi) GetAlertConfig(c *gin.Context) {
|
||||
helper.SuccessWithData(c, config)
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Page alert config
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /alert/config/search [post]
|
||||
func (b *BaseApi) PageAlertConfig(c *gin.Context) {
|
||||
var req dto.PageInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, configs, err := alertService.PageAlertConfig(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Total: total,
|
||||
Items: configs,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Update alert config
|
||||
// @Accept json
|
||||
@@ -260,13 +289,24 @@ func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := alertService.UpdateAlertConfig(req); err != nil {
|
||||
if err := alertService.UpdateAlertConfig(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func loadAuditUser(c *gin.Context) string {
|
||||
userName := strings.TrimSpace(c.GetHeader("X-Panel-User"))
|
||||
if userName == "" {
|
||||
return defaultAuditUser
|
||||
}
|
||||
if decoded, err := url.QueryUnescape(userName); err == nil {
|
||||
return decoded
|
||||
}
|
||||
return userName
|
||||
}
|
||||
|
||||
// @Tags Alert
|
||||
// @Summary Delete alert config
|
||||
// @Accept json
|
||||
|
||||
@@ -21,7 +21,7 @@ func (b *BaseApi) CreateClam(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := clamService.Create(req); err != nil {
|
||||
if err := clamService.Create(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func (b *BaseApi) UpdateClam(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := clamService.Update(req); err != nil {
|
||||
if err := clamService.Update(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func (b *BaseApi) CreateCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cronjobService.Create(req); err != nil {
|
||||
if err := cronjobService.Create(req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (b *BaseApi) ImportCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cronjobService.Import(req.Cronjobs); err != nil {
|
||||
if err := cronjobService.Import(req.Cronjobs, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -285,7 +285,7 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cronjobService.Update(req.ID, req); err != nil {
|
||||
if err := cronjobService.Update(req.ID, req, loadAuditUser(c)); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ type AlertDTO struct {
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
CreateUser string `json:"createUser"`
|
||||
UpdateUser string `json:"updateUser"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ type Alert struct {
|
||||
Count uint `gorm:"type:integer;not null" json:"count"`
|
||||
Project string `gorm:"type:varchar(64)" json:"project"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Method string `gorm:"type:varchar(64);not null" json:"method"`
|
||||
Method string `gorm:"type:text;not null" json:"method"`
|
||||
SendCount uint `gorm:"type:integer" json:"sendCount"`
|
||||
AdvancedParams string `gorm:"type:longText" json:"advancedParams"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
}
|
||||
|
||||
type AlertTask struct {
|
||||
@@ -19,7 +21,7 @@ type AlertTask struct {
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Quota string `gorm:"type:varchar(64)" json:"quota"`
|
||||
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
|
||||
Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
}
|
||||
|
||||
type AlertLog struct {
|
||||
@@ -34,15 +36,17 @@ type AlertLog struct {
|
||||
Message string `gorm:"type:varchar(256);" json:"message"`
|
||||
RecordId uint `gorm:"type:integer;" json:"recordId"`
|
||||
LicenseId string `gorm:"type:varchar(256);not null;" json:"licenseId" `
|
||||
Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"`
|
||||
Method string `gorm:"type:varchar(128);not null;default:'sms'" json:"method"`
|
||||
}
|
||||
|
||||
type AlertConfig struct {
|
||||
BaseModel
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Title string `gorm:"type:varchar(64);not null" json:"title"`
|
||||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||
CreateUser string `gorm:"type:varchar(256)" json:"createUser"`
|
||||
UpdateUser string `gorm:"type:varchar(256)" json:"updateUser"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
|
||||
+78
-11
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"google.golang.org/genproto/googleapis/type/date"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -21,6 +22,7 @@ type IAlertRepo interface {
|
||||
WithByLicenseId(licenseId string) DBOption
|
||||
WithByRecordId(recordId uint) DBOption
|
||||
WithByMethod(method string) DBOption
|
||||
WithByMethodConfigID(id uint) DBOption
|
||||
|
||||
Create(alert *model.Alert) error
|
||||
Get(opts ...DBOption) (model.Alert, error)
|
||||
@@ -47,11 +49,15 @@ type IAlertRepo interface {
|
||||
GetLicensePushCount(method string) (uint, error)
|
||||
|
||||
GetConfig(opts ...DBOption) (model.AlertConfig, error)
|
||||
GetConfigById(id uint) (model.AlertConfig, error)
|
||||
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
|
||||
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
|
||||
CreateAlertConfig(config *model.AlertConfig) error
|
||||
DeleteAlertConfig(opts ...DBOption) error
|
||||
|
||||
WithByTypeNotIn(types []string) DBOption
|
||||
PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error)
|
||||
|
||||
SyncAll(data []model.AlertConfig) error
|
||||
}
|
||||
|
||||
@@ -103,7 +109,14 @@ func (a *AlertRepo) WithByRecordId(recordId uint) DBOption {
|
||||
|
||||
func (a *AlertRepo) WithByMethod(method string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("method = ?", method)
|
||||
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))
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("(method = ? OR method LIKE ? OR method LIKE ? OR method LIKE ?)", method, method+",%", "%,"+method, "%,"+method+",%")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,32 +319,86 @@ func (a *AlertRepo) GetConfig(opts ...DBOption) (model.AlertConfig, error) {
|
||||
return alertConfig, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) GetConfigById(id uint) (model.AlertConfig, error) {
|
||||
var config model.AlertConfig
|
||||
err := global.AlertDB.First(&config, id).Error
|
||||
return config, err
|
||||
}
|
||||
|
||||
func (a *AlertRepo) WithByTypeNotIn(types []string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("`type` NOT IN (?)", types)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRepo) PageAlertConfig(page, size int, opts ...DBOption) (int64, []model.AlertConfig, error) {
|
||||
var configs []model.AlertConfig
|
||||
db := global.AlertDB.Model(&model.AlertConfig{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&configs).Error
|
||||
return count, configs, err
|
||||
}
|
||||
|
||||
var singletonTypes = map[string]bool{
|
||||
constant.CommonConfig: true,
|
||||
}
|
||||
|
||||
func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
|
||||
tx := global.AlertDB.Begin()
|
||||
var oldConfigs []model.AlertConfig
|
||||
_ = tx.Find(&oldConfigs).Error
|
||||
oldConfigMap := make(map[string]uint)
|
||||
nonSingletonTypes := make(map[string]struct{})
|
||||
for _, item := range oldConfigs {
|
||||
oldConfigMap[item.Type] = item.ID
|
||||
if singletonTypes[item.Type] {
|
||||
oldConfigMap[item.Type] = item.ID
|
||||
continue
|
||||
}
|
||||
nonSingletonTypes[item.Type] = struct{}{}
|
||||
}
|
||||
for _, item := range data {
|
||||
if val, ok := oldConfigMap[item.Type]; ok {
|
||||
item.ID = val
|
||||
delete(oldConfigMap, item.Type)
|
||||
} else {
|
||||
item.ID = 0
|
||||
if !singletonTypes[item.Type] {
|
||||
nonSingletonTypes[item.Type] = struct{}{}
|
||||
}
|
||||
if err := tx.Model(model.AlertConfig{}).Where("id = ?", item.ID).Save(&item).Error; err != nil {
|
||||
}
|
||||
for itemType := range nonSingletonTypes {
|
||||
if err := tx.Where("type = ?", itemType).Delete(&model.AlertConfig{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, val := range oldConfigMap {
|
||||
if err := tx.Where("id = ?", val).Delete(&model.AlertConfig{}).Error; err != nil {
|
||||
for _, item := range data {
|
||||
if singletonTypes[item.Type] {
|
||||
if val, ok := oldConfigMap[item.Type]; ok {
|
||||
item.ID = val
|
||||
delete(oldConfigMap, item.Type)
|
||||
} else {
|
||||
item.ID = 0
|
||||
}
|
||||
if err := tx.Model(model.AlertConfig{}).Where("id = ?", item.ID).Save(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
item.ID = 0
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
for _, id := range oldConfigMap {
|
||||
if err := tx.Where("id = ?", id).Delete(&model.AlertConfig{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+126
-10
@@ -25,15 +25,18 @@ import (
|
||||
|
||||
type AlertService struct{}
|
||||
|
||||
var eeHiddenAlertTypes = []string{"licenseException", "panelUpdate", "panelPwdEndTime"}
|
||||
var hiddenAlertConfigTypes = []string{"sms"}
|
||||
|
||||
type IAlertService interface {
|
||||
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
|
||||
GetAlerts() ([]dto.AlertDTO, error)
|
||||
CreateAlert(create dto.AlertCreate) error
|
||||
UpdateAlert(req dto.AlertUpdate) error
|
||||
CreateAlert(create dto.AlertCreate, operator string) error
|
||||
UpdateAlert(req dto.AlertUpdate, operator string) error
|
||||
DeleteAlert(id uint) error
|
||||
GetAlert(id uint) (dto.AlertDTO, error)
|
||||
UpdateStatus(id uint, status string) error
|
||||
ExternalUpdateAlert(req dto.AlertCreate) error
|
||||
ExternalUpdateAlert(req dto.AlertCreate, operator string) error
|
||||
|
||||
GetDisks() ([]dto.DiskDTO, error)
|
||||
PageAlertLogs(req dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error)
|
||||
@@ -42,7 +45,8 @@ type IAlertService interface {
|
||||
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
|
||||
|
||||
GetAlertConfig() ([]model.AlertConfig, error)
|
||||
UpdateAlertConfig(req dto.AlertConfigUpdate) error
|
||||
PageAlertConfig(req dto.PageInfo) (int64, []model.AlertConfig, error)
|
||||
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
|
||||
DeleteAlertConfig(id uint) error
|
||||
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
|
||||
}
|
||||
@@ -56,6 +60,9 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
opts []repo.DBOption
|
||||
result []dto.AlertDTO
|
||||
)
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(eeHiddenAlertTypes))
|
||||
}
|
||||
if search.Status != "" {
|
||||
opts = append(opts, repo.WithByStatus(search.Status))
|
||||
}
|
||||
@@ -82,6 +89,8 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
CreateUser: item.CreateUser,
|
||||
UpdateUser: item.UpdateUser,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
@@ -113,6 +122,8 @@ func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
CreateUser: item.CreateUser,
|
||||
UpdateUser: item.UpdateUser,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
@@ -121,7 +132,7 @@ func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error {
|
||||
var alertID uint
|
||||
var alertInfo model.Alert
|
||||
if create.Project != "" {
|
||||
@@ -138,7 +149,7 @@ func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
}
|
||||
upAlert.ID = alertID
|
||||
err := a.UpdateAlert(upAlert)
|
||||
err := a.UpdateAlert(upAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -147,6 +158,8 @@ func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
if err := copier.Copy(&alertInfo, &create); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
}
|
||||
alertInfo.CreateUser = operator
|
||||
alertInfo.UpdateUser = operator
|
||||
|
||||
if err := alertRepo.Create(&alertInfo); err != nil {
|
||||
return err
|
||||
@@ -157,7 +170,7 @@ func (a AlertService) CreateAlert(create dto.AlertCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
|
||||
func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error {
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["id"] = req.ID
|
||||
@@ -170,6 +183,7 @@ func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
|
||||
upMap["status"] = req.Status
|
||||
upMap["send_count"] = req.SendCount
|
||||
upMap["advanced_params"] = req.AdvancedParams
|
||||
upMap["update_user"] = operator
|
||||
|
||||
if err := alertRepo.Update(upMap, repo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
@@ -460,12 +474,29 @@ func (a AlertService) GetAlertConfig() ([]model.AlertConfig, error) {
|
||||
opts []repo.DBOption
|
||||
configs []model.AlertConfig
|
||||
)
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "intl" {
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(hiddenAlertConfigTypes))
|
||||
}
|
||||
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
|
||||
configs, err := alertRepo.AlertConfigList(opts...)
|
||||
return configs, err
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
func (a AlertService) PageAlertConfig(req dto.PageInfo) (int64, []model.AlertConfig, error) {
|
||||
opts := []repo.DBOption{
|
||||
alertRepo.WithByTypeNotIn([]string{"common"}),
|
||||
repo.WithOrderDesc("created_at"),
|
||||
}
|
||||
if global.CONF.Base.IsEnterprise || global.CONF.Base.Edition == "intl" {
|
||||
opts = append(opts, alertRepo.WithByTypeNotIn(hiddenAlertConfigTypes))
|
||||
}
|
||||
return alertRepo.PageAlertConfig(req.Page, req.PageSize, opts...)
|
||||
}
|
||||
|
||||
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error {
|
||||
if err := a.checkAlertConfigDisplayNameUnique(req); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.ID != 0 {
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["id"] = req.ID
|
||||
@@ -473,6 +504,7 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
upMap["title"] = req.Title
|
||||
upMap["status"] = req.Status
|
||||
upMap["config"] = req.Config
|
||||
upMap["update_user"] = operator
|
||||
if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -481,6 +513,8 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
if err := copier.Copy(&alertConfig, &req); err != nil {
|
||||
return buserr.WithErr("ErrStructTransform", err)
|
||||
}
|
||||
alertConfig.CreateUser = operator
|
||||
alertConfig.UpdateUser = operator
|
||||
if err := alertRepo.CreateAlertConfig(&alertConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -489,7 +523,70 @@ func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdate) error {
|
||||
displayName := alertConfigDisplayName(req.Type, req.Config)
|
||||
if displayName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
configs, err := alertRepo.AlertConfigList(alertRepo.WithByType(req.Type))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
if req.ID != 0 && config.ID == req.ID {
|
||||
continue
|
||||
}
|
||||
if alertConfigDisplayName(config.Type, config.Config) == displayName {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func alertConfigDisplayName(configType, configData string) string {
|
||||
switch configType {
|
||||
case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark:
|
||||
var cfg struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(configData), &cfg); err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(cfg.DisplayName)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertService) DeleteAlertConfig(id uint) error {
|
||||
config, err := alertRepo.GetConfigById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usedAlerts, err := alertRepo.List(alertRepo.WithByMethodConfigID(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 fmt.Errorf("alert config is in use")
|
||||
}
|
||||
return alertRepo.DeleteAlertConfig(repo.WithByID(id))
|
||||
}
|
||||
|
||||
@@ -522,7 +619,7 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
|
||||
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error {
|
||||
upMap := make(map[string]interface{})
|
||||
var newStatus string
|
||||
if updateAlert.SendCount == 0 {
|
||||
@@ -566,7 +663,7 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
|
||||
} else {
|
||||
if updateAlert.Method != "" && updateAlert.Title != "" {
|
||||
updateAlert.Status = newStatus
|
||||
if err := a.CreateAlert(updateAlert); err != nil {
|
||||
if err := a.CreateAlert(updateAlert, operator); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -574,3 +671,22 @@ func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
|
||||
|
||||
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 ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,10 +174,16 @@ func baseTask(baseAlert []dto.AlertDTO) {
|
||||
case "siteEndTime":
|
||||
loadWebsiteInfo(alert)
|
||||
case "panelPwdEndTime":
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
continue
|
||||
}
|
||||
if global.IsMaster {
|
||||
loadPanelPwd(alert)
|
||||
}
|
||||
case "panelUpdate":
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
continue
|
||||
}
|
||||
if global.IsMaster {
|
||||
loadPanelUpdate(alert)
|
||||
}
|
||||
@@ -210,6 +216,9 @@ func resourceTask(resourceAlert []dto.AlertDTO) {
|
||||
loadNodeException(alert)
|
||||
}
|
||||
case "licenseException":
|
||||
if global.CONF.Base.IsEnterprise {
|
||||
continue
|
||||
}
|
||||
if execute && global.IsMaster {
|
||||
loadLicenseException(alert)
|
||||
}
|
||||
@@ -590,94 +599,139 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
|
||||
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
|
||||
for _, m := range methods {
|
||||
m = strings.TrimSpace(m)
|
||||
switch m {
|
||||
case constant.SMS:
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
continue
|
||||
}
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.SMS)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
alertErr := xpack.AlertProvider.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.SMS)
|
||||
case constant.Email:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.Email)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.Email)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, m)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, m, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, m, err)
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, m)
|
||||
case constant.Bark:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, m)
|
||||
if !isValid {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, m)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateBarkAlertLog(create, alertInfo, params, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert %s push failed, err: %v", alertType, m, alertErr.Error())
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, m)
|
||||
default:
|
||||
if configId, err := strconv.ParseUint(m, 10, 64); err == nil {
|
||||
sendAlertsByConfigId(alert, alertType, quota, quotaType, params, uint(configId))
|
||||
} else {
|
||||
sendAlertsByLegacyMethod(alert, alertType, quota, quotaType, params, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendAlertsByConfigId(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, configId uint) {
|
||||
config, err := alertRepo.GetConfigById(configId)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("alert config not found for id %d: %v", configId, err)
|
||||
return
|
||||
}
|
||||
doSendAlert(alert, alertType, quota, quotaType, params, config)
|
||||
}
|
||||
|
||||
func sendAlertsByLegacyMethod(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, method string) {
|
||||
typeMap := map[string]string{
|
||||
"mail": constant.Email,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.SMS: constant.SMS,
|
||||
}
|
||||
configType, ok := typeMap[method]
|
||||
if !ok {
|
||||
configType = method
|
||||
}
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
|
||||
return
|
||||
}
|
||||
doSendAlert(alert, alertType, quota, quotaType, params, config)
|
||||
}
|
||||
|
||||
func doSendAlert(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param, config model.AlertConfig) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
methodStr := strconv.Itoa(int(config.ID))
|
||||
switch config.Type {
|
||||
case constant.SMS:
|
||||
if !alertUtil.CheckSMSSendLimit(config, methodStr) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
alertErr := xpack.AlertProvider.CreateSMSAlertLog(alertType, alert, create, quotaType, params, config, methodStr)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.Email:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo, config)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.Bark:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
alertInfo := alert
|
||||
alertInfo.Type = alertType
|
||||
create.AlertRule = alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Bark)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := alertUtil.CreateBarkAlertLog(create, alertInfo, params, transport, agentInfo, config)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert %s push failed, err: %v", alertType, methodStr, alertErr.Error())
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, methodStr)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertType,
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
alertErr := xpack.AlertProvider.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, config, transport, agentInfo)
|
||||
if alertErr != nil {
|
||||
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, methodStr, alertErr)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, methodStr)
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
func getRepoOptionsByProject(project string) []repo.DBOption {
|
||||
var opts []repo.DBOption
|
||||
|
||||
+273
-120
@@ -1,12 +1,16 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AlertSender struct {
|
||||
@@ -22,115 +26,91 @@ func NewAlertSender(alert dto.AlertDTO, quotaType string) *AlertSender {
|
||||
}
|
||||
|
||||
func (s *AlertSender) Send(quota string, params []dto.Param) {
|
||||
methods := strings.Split(s.alert.Method, ",")
|
||||
for _, method := range methods {
|
||||
method = strings.TrimSpace(method)
|
||||
switch method {
|
||||
case constant.SMS:
|
||||
s.sendSMS(quota, params)
|
||||
case constant.Email:
|
||||
s.sendEmail(quota, params)
|
||||
case constant.Bark:
|
||||
s.sendBark(quota, params)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
s.sendWebhook(quota, params, method)
|
||||
}
|
||||
}
|
||||
s.sendByConfigIds(s.alert.Method, quota, params, false)
|
||||
}
|
||||
|
||||
func (s *AlertSender) ResourceSend(quota string, params []dto.Param) {
|
||||
methods := strings.Split(s.alert.Method, ",")
|
||||
for _, method := range methods {
|
||||
method = strings.TrimSpace(method)
|
||||
switch method {
|
||||
case constant.SMS:
|
||||
s.sendResourceSMS(quota, params)
|
||||
case constant.Email:
|
||||
s.sendResourceEmail(quota, params)
|
||||
case constant.Bark:
|
||||
s.sendResourceBark(quota, params)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
s.sendResourceWebhook(quota, params, method)
|
||||
s.sendByConfigIds(s.alert.Method, quota, params, true)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendByConfigIds(methodStr string, quota string, params []dto.Param, isResource bool) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
configIds := strings.Split(methodStr, ",")
|
||||
for _, idStr := range configIds {
|
||||
idStr = strings.TrimSpace(idStr)
|
||||
configId, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
s.sendByLegacyMethod(idStr, quota, params, isResource)
|
||||
continue
|
||||
}
|
||||
config, err := alertRepo.GetConfigById(uint(configId))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("alert config not found for id %d: %v", configId, err)
|
||||
continue
|
||||
}
|
||||
s.sendByConfig(config, quota, params, isResource)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendByConfig(config model.AlertConfig, quota string, params []dto.Param, isResource bool) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
switch config.Type {
|
||||
case constant.SMS:
|
||||
if isResource {
|
||||
s.sendResourceSMSWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendSMSWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.Email:
|
||||
if isResource {
|
||||
s.sendResourceEmailWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendEmailWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.Bark:
|
||||
if isResource {
|
||||
s.sendResourceBarkWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendBarkWithConfig(config, quota, params)
|
||||
}
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
if isResource {
|
||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||
} else {
|
||||
s.sendWebhookWithConfig(config, quota, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
return
|
||||
func (s *AlertSender) sendByLegacyMethod(method string, quota string, params []dto.Param, isResource bool) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
typeMap := map[string]string{"mail": constant.Email, constant.Bark: constant.Bark, constant.SMS: constant.SMS}
|
||||
configType := method
|
||||
if mapped, ok := typeMap[method]; ok {
|
||||
configType = mapped
|
||||
}
|
||||
|
||||
totalCount, isValid := s.canSendAlert(constant.SMS)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
}
|
||||
|
||||
err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
|
||||
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
s.sendByConfig(config, quota, params, isResource)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
|
||||
totalCount, isValid := s.canSendAlert(constant.Email)
|
||||
if !isValid {
|
||||
func (s *AlertSender) sendSMSWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
method := strconv.Itoa(int(config.ID))
|
||||
if !alertUtil.CheckSMSSendLimit(config, method) {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
|
||||
}
|
||||
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendBark(quota string, params []dto.Param) {
|
||||
totalCount, isValid := s.canSendAlert(constant.Bark)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
|
||||
}
|
||||
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert bark push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Bark)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||
totalCount, isValid := s.canSendAlert(method)
|
||||
if !isValid {
|
||||
return
|
||||
@@ -141,43 +121,60 @@ func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method strin
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: method,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo)
|
||||
|
||||
err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, config, method)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
|
||||
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMS))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendSMSWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.SMS)
|
||||
func (s *AlertSender) sendEmailWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
if err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS); err != nil {
|
||||
global.LOG.Errorf("failed to send SMS alert: %v", err)
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo, config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.Email)
|
||||
func (s *AlertSender) sendResourceEmailWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
@@ -189,19 +186,70 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
|
||||
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo, config); err != nil {
|
||||
global.LOG.Errorf("failed to send Email alert: %v", err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.Bark)
|
||||
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendEmailWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceEmailWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendBarkWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo, config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert bark push failed: %v", s.alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceBarkWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
@@ -213,18 +261,114 @@ func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
|
||||
Type: s.alert.Type,
|
||||
AlertRule: alertUtil.ProcessAlertRule(s.alert),
|
||||
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Bark),
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
|
||||
if err := alertUtil.CreateBarkAlertLog(create, s.alert, params, transport, agentInfo, config); err != nil {
|
||||
global.LOG.Errorf("failed to send Bark alert: %v", err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Bark)
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendBark(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.Bark))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendBarkWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceBark(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.Bark))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceBarkWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
totalCount, isValid := s.canSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: totalCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceWebhookWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
todayCount, isValid := s.canResourceSendAlert(strconv.Itoa(int(config.ID)))
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: strconv.Itoa(int(config.ID)),
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, config, transport, agentInfo); err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, config.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, strconv.Itoa(int(config.ID)))
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(method))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendWebhookWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, method string) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(method))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceWebhookWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMSWithConfig(config model.AlertConfig, quota string, params []dto.Param) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
method := strconv.Itoa(int(config.ID))
|
||||
if !alertUtil.CheckSMSSendLimit(config, method) {
|
||||
return
|
||||
}
|
||||
|
||||
todayCount, isValid := s.canResourceSendAlert(method)
|
||||
if !isValid {
|
||||
return
|
||||
@@ -235,16 +379,25 @@ func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, meth
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
Method: method,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
if err := xpack.AlertProvider.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo); err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
|
||||
|
||||
if err := xpack.AlertProvider.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, config, method); err != nil {
|
||||
global.LOG.Errorf("failed to send SMS alert: %v", err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.sendResourceSMSWithConfig(config, quota, params)
|
||||
}
|
||||
|
||||
func (s *AlertSender) canSendAlert(method string) (uint, bool) {
|
||||
todayCount, totalCount, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,8 +36,8 @@ type IClamService interface {
|
||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||
Operate(operate string) error
|
||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ClamCreate) error
|
||||
Update(req dto.ClamUpdate) error
|
||||
Create(req dto.ClamCreate, operator string) error
|
||||
Update(req dto.ClamUpdate, operator string) error
|
||||
UpdateStatus(id uint, status string) error
|
||||
Delete(req dto.ClamDelete) error
|
||||
HandleOnce(id uint) error
|
||||
@@ -165,7 +165,7 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
func (c *ClamService) Create(req dto.ClamCreate, operator string) error {
|
||||
clam, _ := clamRepo.Get(repo.WithByName(req.Name))
|
||||
if clam.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -199,7 +199,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
Project: strconv.Itoa(int(clam.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert)
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
func (c *ClamService) Update(req dto.ClamUpdate, operator string) error {
|
||||
if cmd.CheckIllegal(req.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
@@ -260,7 +260,7 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
Type: "clams",
|
||||
Project: strconv.Itoa(int(clam.ID)),
|
||||
}
|
||||
err := NewIAlertService().ExternalUpdateAlert(updateAlert)
|
||||
err := NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ type CronjobService struct{}
|
||||
type ICronjobService interface {
|
||||
SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
|
||||
SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
|
||||
Create(cronjobDto dto.CronjobOperate) error
|
||||
Create(cronjobDto dto.CronjobOperate, operator string) error
|
||||
LoadNextHandle(spec string) ([]string, error)
|
||||
HandleOnce(id uint) error
|
||||
Update(id uint, req dto.CronjobOperate) error
|
||||
Update(id uint, req dto.CronjobOperate, operator string) error
|
||||
UpdateStatus(id uint, status string) error
|
||||
UpdateGroup(req dto.ChangeGroup) error
|
||||
Delete(req dto.CronjobBatchDelete) error
|
||||
@@ -39,7 +39,7 @@ type ICronjobService interface {
|
||||
HandleStop(id uint) error
|
||||
|
||||
Export(req dto.OperateByIDs) (string, error)
|
||||
Import(req []dto.CronjobTrans) error
|
||||
Import(req []dto.CronjobTrans, operator string) error
|
||||
LoadScriptOptions() []dto.ScriptOptions
|
||||
|
||||
LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
|
||||
@@ -212,7 +212,7 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
|
||||
return string(itemJson), nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans) error {
|
||||
func (u *CronjobService) Import(req []dto.CronjobTrans, operator string) error {
|
||||
for _, item := range req {
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
|
||||
if cronjobItem.ID != 0 {
|
||||
@@ -405,7 +405,7 @@ func (u *CronjobService) Import(req []dto.CronjobTrans) error {
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
}
|
||||
_ = NewIAlertService().CreateAlert(createAlert)
|
||||
_ = NewIAlertService().CreateAlert(createAlert, operator)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -558,7 +558,7 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate) error {
|
||||
func (u *CronjobService) Create(req dto.CronjobOperate, operator string) error {
|
||||
cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
|
||||
if cronjob.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
@@ -607,7 +607,7 @@ func (u *CronjobService) Create(req dto.CronjobOperate) error {
|
||||
Project: strconv.Itoa(int(cronjob.ID)),
|
||||
Status: constant.AlertEnable,
|
||||
}
|
||||
err := NewIAlertService().CreateAlert(createAlert)
|
||||
err := NewIAlertService().CreateAlert(createAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -678,7 +678,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
|
||||
func (u *CronjobService) Update(id uint, req dto.CronjobOperate, operator string) error {
|
||||
var cronjob model.Cronjob
|
||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
@@ -756,7 +756,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
|
||||
Type: cronjob.Type,
|
||||
Project: strconv.Itoa(int(cronModel.ID)),
|
||||
}
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert)
|
||||
err = NewIAlertService().ExternalUpdateAlert(updateAlert, operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const (
|
||||
const (
|
||||
WeChat = "wechat"
|
||||
SMS = "sms"
|
||||
Email = "mail"
|
||||
Email = "email"
|
||||
WeCom = "weCom"
|
||||
DingTalk = "dingTalk"
|
||||
FeiShu = "feiShu"
|
||||
|
||||
@@ -36,6 +36,8 @@ func InitAgentDB() {
|
||||
migrations.UpdateMcpServer,
|
||||
migrations.InitCronjobGroup,
|
||||
migrations.AddColumnToAlert,
|
||||
migrations.MigrateAlertMethodConfigIDs,
|
||||
migrations.AddAlertAuditUser,
|
||||
migrations.UpdateWebsiteSSL,
|
||||
migrations.AddQuickJump,
|
||||
migrations.UpdateMcpServerAddType,
|
||||
|
||||
@@ -389,16 +389,20 @@ var InitAlertConfig = &gormigrate.Migration{
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
records := []model.AlertConfig{
|
||||
{
|
||||
Type: "sms",
|
||||
Title: "xpack.alert.smsConfig",
|
||||
Status: "Enable",
|
||||
Config: `{"alertDailyNum":50}`,
|
||||
Type: "sms",
|
||||
Title: "xpack.alert.smsConfig",
|
||||
Status: "Enable",
|
||||
Config: `{"alertDailyNum":50}`,
|
||||
CreateUser: "system",
|
||||
UpdateUser: "system",
|
||||
},
|
||||
{
|
||||
Type: "common",
|
||||
Title: "xpack.alert.commonConfig",
|
||||
Status: "Enable",
|
||||
Config: `{"isOffline":"Disable","alertSendTimeRange":{"noticeAlert":{"sendTimeRange":"08:00:00 - 23:59:59","type":["ssl","siteEndTime","panelPwdEndTime","panelUpdate"]},"resourceAlert":{"sendTimeRange":"00:00:00 - 23:59:59","type":["clams","cronJob","cpu","memory","load","disk"]}}}`,
|
||||
Type: "common",
|
||||
Title: "xpack.alert.commonConfig",
|
||||
Status: "Enable",
|
||||
Config: `{"isOffline":"Disable","alertSendTimeRange":{"noticeAlert":{"sendTimeRange":"08:00:00 - 23:59:59","type":["ssl","siteEndTime","panelPwdEndTime","panelUpdate"]},"resourceAlert":{"sendTimeRange":"00:00:00 - 23:59:59","type":["clams","cronJob","cpu","memory","load","disk"]}}}`,
|
||||
CreateUser: "system",
|
||||
UpdateUser: "system",
|
||||
},
|
||||
}
|
||||
for _, r := range records {
|
||||
@@ -477,6 +481,95 @@ var AddColumnToAlert = &gormigrate.Migration{
|
||||
},
|
||||
}
|
||||
|
||||
var MigrateAlertMethodConfigIDs = &gormigrate.Migration{
|
||||
ID: "20251001-migrate-alert-method-config-ids",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertLog{}, &model.AlertTask{}, &model.AlertConfig{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateAlertMethodConfigIDs(global.AlertDB); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAlertAuditUser = &gormigrate.Migration{
|
||||
ID: "20260602-add-alert-audit-user",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertConfig{})
|
||||
},
|
||||
}
|
||||
|
||||
func migrateAlertMethodConfigIDs(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.AlertConfig{}).Where("type = ?", "mail").Update("type", constant.EmailConfig).Error; err != nil {
|
||||
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))
|
||||
}
|
||||
|
||||
var alerts []model.Alert
|
||||
if err := tx.Find(&alerts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, alert := range alerts {
|
||||
method := migrateAlertMethodValue(alert.Method, typeMap, configIDs)
|
||||
if method == alert.Method {
|
||||
continue
|
||||
}
|
||||
if err := tx.Model(&model.Alert{}).Where("id = ?", alert.ID).Update("method", method).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateAlertMethodValue(method string, typeMap map[string]string, configIDs map[string]string) string {
|
||||
items := strings.Split(method, ",")
|
||||
next := make([]string, 0, len(items))
|
||||
seen := map[string]struct{}{}
|
||||
for _, item := range items {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
configType, ok := typeMap[item]
|
||||
if ok {
|
||||
if id, exists := configIDs[configType]; exists {
|
||||
item = id
|
||||
}
|
||||
}
|
||||
if _, exists := seen[item]; exists {
|
||||
continue
|
||||
}
|
||||
seen[item] = struct{}{}
|
||||
next = append(next, item)
|
||||
}
|
||||
return strings.Join(next, ",")
|
||||
}
|
||||
|
||||
var UpdateWebsiteSSL = &gormigrate.Migration{
|
||||
ID: "20250819-update-website-ssl",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
@@ -25,6 +25,7 @@ func (a *AlertRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
|
||||
alertRouter.POST("/config/update", baseApi.UpdateAlertConfig)
|
||||
alertRouter.POST("/config/info", baseApi.GetAlertConfig)
|
||||
alertRouter.POST("/config/search", baseApi.PageAlertConfig)
|
||||
alertRouter.POST("/config/del", baseApi.DeleteAlertConfig)
|
||||
alertRouter.POST("/config/test", baseApi.TestAlertConfig)
|
||||
}
|
||||
|
||||
+23
-30
@@ -4,6 +4,16 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
network "net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
@@ -16,29 +26,20 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/psutil"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"github.com/jinzhu/copier"
|
||||
"mime"
|
||||
network "net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"}
|
||||
|
||||
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
|
||||
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error {
|
||||
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
|
||||
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
|
||||
alertRule := ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
create.AlertDetail = alertDetail
|
||||
return CreateEmailAlertLog(create, alert, params, transport, agentInfo)
|
||||
return CreateEmailAlertLog(create, alert, params, transport, agentInfo, emailConfig)
|
||||
}
|
||||
|
||||
func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport, agentInfo *dto.AgentInfo) error {
|
||||
func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport, agentInfo *dto.AgentInfo, emailConfig model.AlertConfig) error {
|
||||
var alertLog model.AlertLog
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
|
||||
@@ -50,11 +51,6 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
create.Method = constant.Email
|
||||
emailConfig, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var emailInfo dto.AlertEmailConfig
|
||||
err = json.Unmarshal([]byte(emailConfig.Config), &emailInfo)
|
||||
if err != nil {
|
||||
@@ -104,17 +100,11 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
|
||||
}
|
||||
}
|
||||
|
||||
func CreateBarkAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport, agentInfo *dto.AgentInfo) error {
|
||||
func CreateBarkAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport, agentInfo *dto.AgentInfo, barkConfig model.AlertConfig) error {
|
||||
var alertLog model.AlertLog
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
|
||||
create.Method = constant.Bark
|
||||
barkConfig, err := alertRepo.GetConfig(alertRepo.WithByType(constant.Bark))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var barkInfo dto.AlertWebhookConfig
|
||||
err = json.Unmarshal([]byte(barkConfig.Config), &barkInfo)
|
||||
err := json.Unmarshal([]byte(barkConfig.Config), &barkInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -244,14 +234,13 @@ func CreateAlertParams(param string) []dto.Param {
|
||||
|
||||
var checkTaskMutex sync.Mutex
|
||||
|
||||
func CheckSMSSendLimit(method string) bool {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
|
||||
if err != nil {
|
||||
func CheckSMSSendLimit(config model.AlertConfig, method string) bool {
|
||||
if config.Type != constant.SMS {
|
||||
return false
|
||||
}
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
var cfg dto.AlertSmsConfig
|
||||
cfg, err = ParseAlertSmsConfig(config.Config)
|
||||
cfg, err := ParseAlertSmsConfig(config.Config)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -273,6 +262,10 @@ func CheckSMSSendLimit(method string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func IsAlertConfigEnabled(config model.AlertConfig) bool {
|
||||
return config.Status == constant.AlertEnable
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
NoticeAlert Category `json:"noticeAlert"`
|
||||
ResourceAlert Category `json:"resourceAlert"`
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package alert_push
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/jinzhu/copier"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PushAlert(pushAlert dto.PushAlert) error {
|
||||
@@ -28,87 +30,132 @@ func PushAlert(pushAlert dto.PushAlert) error {
|
||||
methods := strings.Split(alert.Method, ",")
|
||||
for _, m := range methods {
|
||||
m = strings.TrimSpace(m)
|
||||
switch m {
|
||||
case constant.SMS:
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
continue
|
||||
}
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
err = xpack.AlertProvider.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert sms push failed: %v", alert.Type, err)
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS)
|
||||
case constant.Email:
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert email push failed: %v", alert.Type, err)
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email)
|
||||
case constant.Bark:
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Bark)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param))
|
||||
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
|
||||
alertRule := alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
create.AlertDetail = alertDetail
|
||||
err = alertUtil.CreateBarkAlertLog(create, alert, params, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert bark push failed: %v", alert.Type, err)
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Bark)
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), m)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
continue
|
||||
}
|
||||
var create = dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err = xpack.AlertProvider.CreateTaskScanWebhookAlertLog(alert, alert.Type, create, pushAlert, m, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", alert.Type, m, err)
|
||||
continue
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), m)
|
||||
default:
|
||||
if configId, err := strconv.ParseUint(m, 10, 64); err == nil {
|
||||
pushByConfigId(alertRepo, alert, pushAlert, uint(configId))
|
||||
} else {
|
||||
pushByLegacyMethod(alertRepo, alert, pushAlert, m)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushByConfigId(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert dto.PushAlert, configId uint) {
|
||||
config, err := alertRepo.GetConfigById(configId)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("alert config not found for id %d: %v", configId, err)
|
||||
return
|
||||
}
|
||||
sendAlert(alertRepo, alert, pushAlert, config)
|
||||
}
|
||||
|
||||
func pushByLegacyMethod(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert dto.PushAlert, method string) {
|
||||
typeMap := map[string]string{
|
||||
"mail": constant.Email,
|
||||
constant.Bark: constant.Bark,
|
||||
constant.SMS: constant.SMS,
|
||||
}
|
||||
configType := method
|
||||
if mapped, ok := typeMap[method]; ok {
|
||||
configType = mapped
|
||||
}
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(configType))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("alert config not found for type %s: %v", configType, err)
|
||||
return
|
||||
}
|
||||
sendAlert(alertRepo, alert, pushAlert, config)
|
||||
}
|
||||
|
||||
func sendAlert(alertRepo repo.IAlertRepo, alert dto.AlertDTO, pushAlert dto.PushAlert, config model.AlertConfig) {
|
||||
if !alertUtil.IsAlertConfigEnabled(config) {
|
||||
return
|
||||
}
|
||||
methodStr := strconv.Itoa(int(config.ID))
|
||||
switch config.Type {
|
||||
case constant.SMS:
|
||||
if !alertUtil.CheckSMSSendLimit(config, methodStr) {
|
||||
return
|
||||
}
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
err = xpack.AlertProvider.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, config, methodStr)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert sms push failed: %v", alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
|
||||
case constant.Email:
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport, agentInfo, config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert email push failed: %v", alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
|
||||
case constant.Bark:
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
params := alertUtil.CreateAlertParams(alertUtil.GetCronJobTypeName(pushAlert.Param))
|
||||
alertDetail := alertUtil.ProcessAlertDetail(alert, pushAlert.TaskName, params, constant.Bark)
|
||||
alertRule := alertUtil.ProcessAlertRule(alert)
|
||||
create.AlertRule = alertRule
|
||||
create.AlertDetail = alertDetail
|
||||
err = alertUtil.CreateBarkAlertLog(create, alert, params, transport, agentInfo, config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert bark push failed: %v", alert.Type, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
|
||||
case constant.WeCom, constant.DingTalk, constant.FeiShu:
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
if err != nil || alert.SendCount <= todayCount {
|
||||
return
|
||||
}
|
||||
create := dto.AlertLogCreate{
|
||||
Type: alertUtil.GetCronJobType(alert.Type),
|
||||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
Method: methodStr,
|
||||
}
|
||||
transport := xpack.MultiNodeProvider.LoadRequestTransport()
|
||||
agentInfo, _ := xpack.MultiNodeProvider.GetAgentInfo()
|
||||
err = xpack.AlertProvider.CreateTaskScanWebhookAlertLog(alert, alert.Type, create, pushAlert, config, transport, agentInfo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s alert %s webhook push failed: %v", alert.Type, methodStr, err)
|
||||
return
|
||||
}
|
||||
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), methodStr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -13,20 +19,63 @@ func NewIAlertProvider() providers.AlertProvider {
|
||||
return &alertHelper{}
|
||||
}
|
||||
|
||||
func (a *alertHelper) CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
|
||||
return nil
|
||||
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{})
|
||||
}
|
||||
|
||||
func (a *alertHelper) CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
|
||||
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{})
|
||||
}
|
||||
|
||||
func (a *alertHelper) CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
|
||||
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)
|
||||
}
|
||||
|
||||
func (a *alertHelper) CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
|
||||
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{})
|
||||
}
|
||||
|
||||
func (a *alertHelper) GetLicenseErrorAlert() (uint, error) {
|
||||
|
||||
@@ -4,14 +4,15 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
type AlertProvider interface {
|
||||
GetNodeErrorAlert() (uint, error)
|
||||
GetLicenseErrorAlert() (uint, error)
|
||||
|
||||
CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error
|
||||
CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error
|
||||
CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error
|
||||
CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error
|
||||
CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, config model.AlertConfig, method string) error
|
||||
CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, config model.AlertConfig, method string) error
|
||||
CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, config model.AlertConfig, transport *http.Transport, agentInfo *dto.AgentInfo) error
|
||||
CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, config model.AlertConfig, transport *http.Transport, agentInfo *dto.AgentInfo) error
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package constant
|
||||
const (
|
||||
WeChat = "wechat"
|
||||
SMS = "sms"
|
||||
Email = "mail"
|
||||
Email = "email"
|
||||
WeCom = "weCom"
|
||||
DingTalk = "dingTalk"
|
||||
FeiShu = "feiShu"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package helper
|
||||
|
||||
type AlertAuditAlert struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreateUser string `gorm:"type:varchar(256)"`
|
||||
UpdateUser string `gorm:"type:varchar(256)"`
|
||||
}
|
||||
|
||||
func (AlertAuditAlert) TableName() string {
|
||||
return "alerts"
|
||||
}
|
||||
|
||||
type AlertAuditConfig struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreateUser string `gorm:"type:varchar(256)"`
|
||||
UpdateUser string `gorm:"type:varchar(256)"`
|
||||
}
|
||||
|
||||
func (AlertAuditConfig) TableName() string {
|
||||
return "alert_configs"
|
||||
}
|
||||
@@ -46,6 +46,7 @@ func Init() {
|
||||
migrations.AddOperationLogUser,
|
||||
migrations.AddLoginLogUser,
|
||||
migrations.AddIsOfflineSetting,
|
||||
migrations.AddAlertAuditUser,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/init/migration/helper"
|
||||
@@ -1284,3 +1285,44 @@ var AddIsOfflineSetting = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAlertAuditUser = &gormigrate.Migration{
|
||||
ID: "20260602-add-alert-audit-user",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if !global.AlertDB.Migrator().HasTable(&helper.AlertAuditAlert{}) || !global.AlertDB.Migrator().HasTable(&helper.AlertAuditConfig{}) {
|
||||
return nil
|
||||
}
|
||||
if err := global.AlertDB.AutoMigrate(&helper.AlertAuditAlert{}, &helper.AlertAuditConfig{}); err != nil {
|
||||
return err
|
||||
}
|
||||
username, err := repo.NewISettingRepo().GetValueByKey("UserName")
|
||||
if err != nil || strings.TrimSpace(username) == "" {
|
||||
username = global.CONF.Base.Username
|
||||
}
|
||||
username = strings.TrimSpace(username)
|
||||
if username == "" {
|
||||
return nil
|
||||
}
|
||||
updates := []struct {
|
||||
table string
|
||||
field string
|
||||
}{
|
||||
{table: "alerts", field: "create_user"},
|
||||
{table: "alerts", field: "update_user"},
|
||||
{table: "alert_configs", field: "create_user"},
|
||||
{table: "alert_configs", field: "update_user"},
|
||||
}
|
||||
for _, item := range updates {
|
||||
if err := global.AlertDB.Exec(fmt.Sprintf(
|
||||
"UPDATE %s SET %s = ? WHERE %s IS NULL OR %s = ''",
|
||||
item.table,
|
||||
item.field,
|
||||
item.field,
|
||||
item.field,
|
||||
), username).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ func Proxy() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if userName := middleware.LoadOperationUser(c); userName != "" {
|
||||
c.Request.Header.Set("X-Panel-User", url.QueryEscape(userName))
|
||||
}
|
||||
|
||||
if reqPath == "/api/v2/hosts/terminal/local" && (currentNode == "local" || len(currentNode) == 0) {
|
||||
proxyLocalAgent(c)
|
||||
return
|
||||
|
||||
@@ -113,7 +113,7 @@ func OperationLog() gin.HandlerFunc {
|
||||
|
||||
c.Next()
|
||||
|
||||
record.User = loadOperationUser(c)
|
||||
record.User = LoadOperationUser(c)
|
||||
|
||||
if len(operationDic.BeforeFunctions) != 0 {
|
||||
if needAgentResolve {
|
||||
@@ -181,7 +181,7 @@ func OperationLog() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func loadOperationUser(c *gin.Context) string {
|
||||
func LoadOperationUser(c *gin.Context) string {
|
||||
sessionUser, ok := c.Get(psessionUtils.GinContextSessionUserKey)
|
||||
if ok {
|
||||
psession, ok := sessionUser.(psessionUtils.SessionUser)
|
||||
|
||||
@@ -14,6 +14,8 @@ export namespace Alert {
|
||||
sendCount: number;
|
||||
sendMethod: string[];
|
||||
advancedParams: string;
|
||||
createUser?: string;
|
||||
updateUser?: string;
|
||||
}
|
||||
|
||||
export interface AlertDetail {
|
||||
@@ -138,6 +140,13 @@ export namespace Alert {
|
||||
title: string;
|
||||
config: string;
|
||||
status: string;
|
||||
createUser?: string;
|
||||
updateUser?: string;
|
||||
}
|
||||
|
||||
export interface AlertConfigPageReq {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface AlertConfigUpdateReq {
|
||||
@@ -188,6 +197,7 @@ export namespace Alert {
|
||||
port?: number;
|
||||
encryption?: string;
|
||||
recipient?: string;
|
||||
recipients?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -210,6 +220,12 @@ export namespace Alert {
|
||||
config: {
|
||||
displayName?: string;
|
||||
url?: string;
|
||||
webhooks?: WebhookItem[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface WebhookItem {
|
||||
displayName: string;
|
||||
url: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,15 @@ export const ListAlertConfigs = (currentNode?: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const PageAlertConfigs = (req: Alert.AlertConfigPageReq, currentNode?: string) => {
|
||||
return http.post<ResPage<Alert.AlertConfigInfo>>(
|
||||
`/alert/config/search`,
|
||||
req,
|
||||
undefined,
|
||||
currentNode ? { CurrentNode: currentNode } : undefined,
|
||||
);
|
||||
};
|
||||
|
||||
export const DeleteAlertConfig = (req: Alert.DelReq) => {
|
||||
return http.post<any>(`/alert/config/del`, req);
|
||||
};
|
||||
|
||||
@@ -123,9 +123,11 @@ const message = {
|
||||
group: 'Group',
|
||||
default: 'Default',
|
||||
createdAt: 'Created',
|
||||
creator: 'Creator',
|
||||
publishedAt: 'Published',
|
||||
date: 'Date',
|
||||
updatedAt: 'Updated',
|
||||
updater: 'Updater',
|
||||
operate: 'Actions',
|
||||
message: 'Message',
|
||||
description: 'Description',
|
||||
@@ -228,6 +230,7 @@ const message = {
|
||||
requiredSelect: 'Select an item in the list',
|
||||
illegalChar: 'Injection of characters & ; $ \' ` ( ) " > < | is currently not supported',
|
||||
illegalInput: "This field mustn't contains illegal characters.",
|
||||
duplicate: 'This value must be unique.',
|
||||
commonName:
|
||||
'This field must start with non-special characters and must consist of English, Chinese, numbers, ".", "-", and "_" characters with a length of 1-128.',
|
||||
userName: 'This field must consist of English, Chinese, numbers and "_" characters with a length of 3-30.',
|
||||
@@ -5453,6 +5456,13 @@ const message = {
|
||||
webhookName: 'Bot name',
|
||||
webhookUrl: 'Webhook URL',
|
||||
alertConfigProHelper: 'Commercial Edition also supports WeCom, DingTalk, Feishu, and SMS alerts.',
|
||||
recipientPlaceholder: 'Please enter recipient email address',
|
||||
addRecipient: 'Add Recipient',
|
||||
webhookItem: 'Webhook',
|
||||
addWebhook: 'Add Webhook',
|
||||
selectAlertType: 'Select Alert Type',
|
||||
configDetail: 'Config Details',
|
||||
webhookCount: ' Webhook(s)',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: 'LXware Gold',
|
||||
|
||||
@@ -229,6 +229,7 @@ const message = {
|
||||
requiredSelect: 'Seleccione un elemento de la lista',
|
||||
illegalChar: 'Actualmente no se admite la inyección de caracteres & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: 'Este campo no debe contener caracteres no permitidos.',
|
||||
duplicate: 'Este valor debe ser único.',
|
||||
commonName:
|
||||
'Este campo debe comenzar con un carácter no especial y debe estar compuesto por letras, caracteres chinos, números, ".", "-", y "_" con una longitud de 1 a 128.',
|
||||
userName:
|
||||
|
||||
@@ -228,6 +228,7 @@ const message = {
|
||||
requiredSelect: 'リスト内のアイテムを選択します',
|
||||
illegalChar: '現在、文字 & ; $ \' ` ( ) " > < | の注入はサポートされていません',
|
||||
illegalInput: 'このフィールドには違法なキャラクターが含まれてはなりません。',
|
||||
duplicate: 'この値は一意である必要があります。',
|
||||
commonName:
|
||||
'このフィールドは、特別なキャラクターではなく、英語、中国語、数字で構成されている必要があります。「。」、「」、および「_」文字が1〜128の文字で構成されている必要があります。',
|
||||
userName: '特殊文字で始まらない、英字、漢字、数字、および_をサポート、長さ3-30',
|
||||
|
||||
@@ -226,6 +226,7 @@ const message = {
|
||||
requiredSelect: '목록에서 항목을 선택하세요',
|
||||
illegalChar: '현재 & ; $ \' ` ( ) " > < | 문자 주입은 지원되지 않습니다',
|
||||
illegalInput: '이 필드에는 유효하지 않은 문자가 포함될 수 없습니다.',
|
||||
duplicate: '이 값은 고유해야 합니다.',
|
||||
commonName:
|
||||
'이 필드는 특수 문자로 시작할 수 없으며, 영어, 한자, 숫자, ".", "-", "_" 문자로 구성되어야 하며 길이는 1-128자여야 합니다.',
|
||||
userName: '특수 문자로 시작하지 않고, 영어, 한국어, 숫자 및 _, 길이 3-30 지원',
|
||||
|
||||
@@ -229,6 +229,7 @@ const message = {
|
||||
requiredSelect: 'Pilih satu item dalam senarai',
|
||||
illegalChar: 'Suntikan aksara & ; $ \' ` ( ) " > < | tidak disokong buat masa ini',
|
||||
illegalInput: 'Ruangan ini tidak boleh mengandungi aksara tidak sah.',
|
||||
duplicate: 'Nilai ini mesti unik.',
|
||||
commonName:
|
||||
'Ruangan ini mesti bermula dengan aksara bukan khas dan mesti terdiri daripada aksara rumi, Cina, nombor, ".", "-", dan "_" dengan panjang 1-128 aksara.',
|
||||
userName:
|
||||
|
||||
@@ -227,6 +227,7 @@ const message = {
|
||||
requiredSelect: 'Selecione um item na lista',
|
||||
illegalChar: 'Atualmente não há suporte para injeção dos caracteres & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: 'Este campo não deve conter caracteres ilegais.',
|
||||
duplicate: 'Este valor deve ser único.',
|
||||
commonName:
|
||||
'Este campo deve começar com caracteres não especiais e consistir em letras, números, ".", "-", e "_" com comprimento de 1-128.',
|
||||
userName: 'Suporta não começar com caracteres especiais, inglês, chinês, números e _, comprimento 3-30',
|
||||
|
||||
@@ -227,6 +227,7 @@ const message = {
|
||||
requiredSelect: 'Выберите элемент из списка',
|
||||
illegalChar: 'В настоящее время не поддерживается вставка символов & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: 'Это поле не должно содержать недопустимых символов.',
|
||||
duplicate: 'Значение должно быть уникальным.',
|
||||
commonName:
|
||||
'Это поле должно начинаться с неспециальных символов и должно состоять из английских букв, китайских иероглифов, цифр, ".", "-" и "_" длиной 1-128.',
|
||||
userName: 'Поддерживает начало без специальных символов, английский, китайский, цифры и _, длина 3-30',
|
||||
|
||||
@@ -231,6 +231,7 @@ const message = {
|
||||
requiredSelect: 'Listeden bir öğe seçin',
|
||||
illegalChar: '& ; $ ` ( ) " > < | karakterlerinin enjekte edilmesi şu anda desteklenmiyor',
|
||||
illegalInput: 'Bu alan yasadışı karakterler içermemelidir.',
|
||||
duplicate: 'Bu değer benzersiz olmalıdır.',
|
||||
commonName:
|
||||
'Bu alan özel olmayan karakterlerle başlamalı ve İngilizce, Çince, rakam, ".", "-", ve "_" karakterlerinden oluşmalı, uzunluk 1-128 olmalıdır.',
|
||||
userName: 'Bu alan İngilizce, Çince, rakam ve "_" karakterlerinden oluşmalı, uzunluk 3-30 olmalıdır.',
|
||||
|
||||
@@ -115,8 +115,10 @@ const message = {
|
||||
group: '分组',
|
||||
default: '默认',
|
||||
createdAt: '创建时间',
|
||||
creator: '创建人',
|
||||
date: '时间',
|
||||
updatedAt: '更新时间',
|
||||
updater: '更新人',
|
||||
operate: '操作',
|
||||
message: '信息',
|
||||
description: '描述',
|
||||
@@ -212,6 +214,7 @@ const message = {
|
||||
requiredSelect: '请选择必选项',
|
||||
illegalChar: '暂不支持注入字符 & ; $ \' ` ( ) " > < |',
|
||||
illegalInput: '输入框中存在不合法字符',
|
||||
duplicate: '内容不能重复',
|
||||
commonName: '支持非特殊字符开头,英文、中文、数字、.-和_,长度1-128',
|
||||
userName: '支持非特殊字符开头、英文、中文、数字和_,长度3-30',
|
||||
simpleName: '支持非下划线开头,英文、数字、_,长度3-30',
|
||||
@@ -4887,6 +4890,7 @@ const message = {
|
||||
dingTalk: '钉钉通知',
|
||||
feiShu: '飞书通知',
|
||||
mail: '邮箱通知',
|
||||
email: '邮箱通知',
|
||||
weCom: '企业微信',
|
||||
bark: 'Bark',
|
||||
sendCountRulesHelper: '到期前发送告警的总数(每日仅发送一次)',
|
||||
@@ -5059,6 +5063,13 @@ const message = {
|
||||
webhookName: '机器人名称',
|
||||
webhookUrl: 'Webhook 地址',
|
||||
alertConfigProHelper: '商业版额外支持企业微信、钉钉、飞书及短信告警。',
|
||||
recipientPlaceholder: '请输入收件人邮箱地址',
|
||||
addRecipient: '添加收件人',
|
||||
webhookItem: 'Webhook',
|
||||
addWebhook: '添加 Webhook',
|
||||
selectAlertType: '选择告警类型',
|
||||
configDetail: '配置详情',
|
||||
webhookCount: '个 Webhook',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: '凌霞金',
|
||||
|
||||
@@ -712,45 +712,44 @@
|
||||
>
|
||||
<el-select
|
||||
class="selectClass"
|
||||
popper-class="alert-config-method-dropdown"
|
||||
v-model="form.alertMethodItems"
|
||||
multiple
|
||||
cleanable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="3"
|
||||
>
|
||||
<el-option value="mail" :label="$t('xpack.alert.mail')" />
|
||||
<el-option
|
||||
v-if="!isProductPro"
|
||||
value="bark"
|
||||
:label="$t('xpack.alert.bark')"
|
||||
/>
|
||||
<el-option
|
||||
value="weCom"
|
||||
v-if="!isIntl"
|
||||
:disabled="!form.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.weCom')"
|
||||
/>
|
||||
<el-option
|
||||
value="dingTalk"
|
||||
v-if="!isIntl"
|
||||
:disabled="!form.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.dingTalk')"
|
||||
/>
|
||||
<el-option
|
||||
value="feiShu"
|
||||
v-if="!isIntl"
|
||||
:disabled="!form.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.feiShu')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="isProductPro"
|
||||
value="bark"
|
||||
:label="$t('xpack.alert.bark')"
|
||||
/>
|
||||
<el-option
|
||||
value="sms"
|
||||
v-if="!isIntl || !isEE"
|
||||
:disabled="!form.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.sms')"
|
||||
/>
|
||||
<el-option-group
|
||||
v-for="group in groupedAlertConfigOptions"
|
||||
:key="group.type"
|
||||
:label="
|
||||
i18n.global.t(
|
||||
'xpack.alert.' + (group.type === 'email' ? 'mail' : group.type),
|
||||
)
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in group.options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:label="opt.label"
|
||||
>
|
||||
<div class="alert-config-option">
|
||||
<span class="alert-config-option__name" :title="opt.label">
|
||||
{{ opt.label }}
|
||||
</span>
|
||||
<el-tag
|
||||
class="alert-config-option__tag"
|
||||
effect="light"
|
||||
size="small"
|
||||
round
|
||||
>
|
||||
{{ opt.typeLabel }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</LayoutCol>
|
||||
@@ -837,7 +836,7 @@ import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
import InputTag from '@/components/input-tag/index.vue';
|
||||
import LayoutCol from '@/components/layout-col/form.vue';
|
||||
import CleanLogConfig from '@/views/cronjob/cronjob/config/clean-log.vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { reactive, ref, computed, onMounted } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { listBackupOptions } from '@/api/modules/backup';
|
||||
import i18n from '@/lang';
|
||||
@@ -851,6 +850,8 @@ import { useRouter } from 'vue-router';
|
||||
import { listContainer } from '@/api/modules/container';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import { listAppInstalled } from '@/api/modules/app';
|
||||
import { Alert } from '@/api/interface/alert';
|
||||
import { ListAlertConfigs } from '@/api/modules/alert';
|
||||
import {
|
||||
loadDefaultSpec,
|
||||
loadDefaultSpecCustom,
|
||||
@@ -883,6 +884,88 @@ const baseDir = ref();
|
||||
|
||||
const isCreate = ref();
|
||||
const defaultGroupID = ref();
|
||||
|
||||
const alertConfigs = ref<Alert.AlertConfigInfo[]>([]);
|
||||
const loadAlertConfigs = async () => {
|
||||
try {
|
||||
const res = await ListAlertConfigs();
|
||||
alertConfigs.value = res.data || [];
|
||||
} catch {}
|
||||
};
|
||||
onMounted(() => {
|
||||
loadAlertConfigs();
|
||||
});
|
||||
|
||||
const alertConfigOptions = computed(() => {
|
||||
const hiddenTypes: string[] = [];
|
||||
if (isIntl.value || isEE.value) hiddenTypes.push('sms');
|
||||
return alertConfigs.value
|
||||
.filter((c) => c.status === 'Enable' && c.type !== 'common' && !hiddenTypes.includes(c.type))
|
||||
.map((c) => ({
|
||||
value: String(c.id),
|
||||
label: getAlertConfigOptionLabel(c),
|
||||
type: c.type,
|
||||
}));
|
||||
});
|
||||
|
||||
const legacyAlertMethodTypeMap: Record<string, string> = {
|
||||
mail: 'email',
|
||||
email: 'email',
|
||||
sms: 'sms',
|
||||
bark: 'bark',
|
||||
weCom: 'weCom',
|
||||
dingTalk: 'dingTalk',
|
||||
feiShu: 'feiShu',
|
||||
};
|
||||
|
||||
const normalizeAlertMethodItems = (methods: string[]) => {
|
||||
return methods.map((method) => {
|
||||
if (/^\d+$/.test(method)) return method;
|
||||
const configType = legacyAlertMethodTypeMap[method];
|
||||
const matched = alertConfigOptions.value.find((item) => item.type === configType);
|
||||
return matched?.value || method;
|
||||
});
|
||||
};
|
||||
|
||||
const groupedAlertConfigOptions = computed(() => {
|
||||
const typeMap = new Map<string, { value: string; label: string }[]>();
|
||||
for (const opt of alertConfigOptions.value) {
|
||||
if (!typeMap.has(opt.type)) typeMap.set(opt.type, []);
|
||||
typeMap.get(opt.type)!.push({ value: opt.value, label: opt.label });
|
||||
}
|
||||
const groups: {
|
||||
type: string;
|
||||
options: { value: string; label: string; typeLabel: string }[];
|
||||
}[] = [];
|
||||
const typeOrder = ['email', 'sms', 'weCom', 'dingTalk', 'feiShu', 'bark'];
|
||||
for (const t of typeOrder) {
|
||||
if (typeMap.has(t)) {
|
||||
const typeLabel = getConfigTypeLabel(t);
|
||||
groups.push({
|
||||
type: t,
|
||||
options: typeMap.get(t)!.map((item) => ({
|
||||
...item,
|
||||
typeLabel,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
|
||||
const getConfigTypeLabel = (type: string): string => {
|
||||
return i18n.global.t(`xpack.alert.${type === 'email' ? 'mail' : type}`);
|
||||
};
|
||||
|
||||
const getAlertConfigOptionLabel = (c: Alert.AlertConfigInfo): string => {
|
||||
try {
|
||||
const cfg = JSON.parse(c.config || '{}');
|
||||
return cfg.displayName || i18n.global.t(`xpack.alert.${c.type === 'email' ? 'mail' : c.type}`);
|
||||
} catch {
|
||||
return i18n.global.t(`xpack.alert.${c.type === 'email' ? 'mail' : c.type}`);
|
||||
}
|
||||
};
|
||||
|
||||
const form = reactive<Cronjob.CronjobInfo>({
|
||||
id: 0,
|
||||
name: '',
|
||||
@@ -1040,7 +1123,7 @@ const search = async () => {
|
||||
form.alertCount = res.data.alertCount || 3;
|
||||
form.alertTitle = res.data.alertTitle;
|
||||
if (res.data.alertMethod) {
|
||||
form.alertMethodItems = res.data.alertMethod.split(',') || [];
|
||||
form.alertMethodItems = normalizeAlertMethodItems(res.data.alertMethod.split(',') || []);
|
||||
} else {
|
||||
form.alertMethodItems = [];
|
||||
}
|
||||
@@ -1644,6 +1727,36 @@ onMounted(() => {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.alert-config-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.alert-config-option__name {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.alert-config-option__tag {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
:global(.alert-config-method-dropdown .el-select-dropdown__item) {
|
||||
padding-right: 52px;
|
||||
}
|
||||
|
||||
:global(.alert-config-method-dropdown .el-select-dropdown__item.is-selected::after) {
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.logText {
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
/>
|
||||
<el-option value="panelLogin" :label="$t('xpack.alert.panelLogin')" />
|
||||
<el-option
|
||||
v-if="isProductPro"
|
||||
v-if="isProductPro && !isEE"
|
||||
value="licenseException"
|
||||
:label="$t('xpack.alert.licenseException')"
|
||||
/>
|
||||
<el-option
|
||||
v-if="isProductPro && !isEE.value"
|
||||
v-if="isProductPro && !isEE"
|
||||
value="nodeException"
|
||||
:label="$t('xpack.alert.nodeException')"
|
||||
/>
|
||||
@@ -131,6 +131,17 @@
|
||||
{{ formatRule(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isEE"
|
||||
:label="$t('commons.table.creator')"
|
||||
prop="createUser"
|
||||
width="100px"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.createUser || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="2"
|
||||
width="130px"
|
||||
@@ -154,7 +165,7 @@ import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import AddTask from '@/views/setting/alert/dash/task/index.vue';
|
||||
import { Alert } from '@/api/interface/alert';
|
||||
import { UpdateAlertStatus, SearchAlerts, DeleteAlert } from '@/api/modules/alert';
|
||||
import { UpdateAlertStatus, SearchAlerts, DeleteAlert, PageAlertConfigs } from '@/api/modules/alert';
|
||||
|
||||
const { isMobile, isMaster, isProductPro, isEE } = useGlobalStore();
|
||||
|
||||
@@ -261,13 +272,46 @@ const formatRule = (row: Alert.AlertInfo) => {
|
||||
return ruleTemplates[row.type] ? ruleTemplates[row.type]() : '';
|
||||
};
|
||||
|
||||
const configMap = ref<Map<string, Alert.AlertConfigInfo>>(new Map());
|
||||
|
||||
const loadConfigMap = async () => {
|
||||
try {
|
||||
const res = await PageAlertConfigs({ page: 1, pageSize: 1000 });
|
||||
const map = new Map<string, Alert.AlertConfigInfo>();
|
||||
for (const c of res.data?.items || []) {
|
||||
map.set(String(c.id), c);
|
||||
}
|
||||
configMap.value = map;
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const formatMethod = (row: Alert.AlertInfo) => {
|
||||
if (!row.method) return '';
|
||||
|
||||
const sendMethod = row.method.split(',').filter(Boolean);
|
||||
const methodStr = sendMethod.map((item) => t('xpack.alert.' + item)).join('|');
|
||||
const resolveMethodLabel = (method: string) => {
|
||||
const config = configMap.value.get(method);
|
||||
if (config) {
|
||||
const typeLabel = i18n.global.t(`xpack.alert.${config.type === 'email' ? 'mail' : config.type}`);
|
||||
try {
|
||||
const cfg = JSON.parse(config.config || '{}');
|
||||
const name = cfg.displayName || cfg.sender || cfg.phone || '';
|
||||
return name ? `${name}(${typeLabel})` : typeLabel;
|
||||
} catch {
|
||||
return typeLabel;
|
||||
}
|
||||
}
|
||||
const oldLabel = i18n.global.t(`xpack.alert.${method}`);
|
||||
if (!oldLabel || oldLabel === `xpack.alert.${method}` || oldLabel.includes('.')) {
|
||||
return /^\d+$/.test(method) ? `#${method}` : method;
|
||||
}
|
||||
return oldLabel;
|
||||
};
|
||||
|
||||
return `「${methodStr}」`;
|
||||
return `「${row.method
|
||||
.split(',')
|
||||
.filter(Boolean)
|
||||
.map((item) => resolveMethodLabel(item.trim()))
|
||||
.join('|')}」`;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
@@ -285,6 +329,7 @@ const search = async () => {
|
||||
order: paginationConfig.order,
|
||||
};
|
||||
try {
|
||||
await loadConfigMap();
|
||||
const res = await SearchAlerts(params);
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total || 0;
|
||||
|
||||
@@ -319,34 +319,40 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('xpack.alert.alertMethod')" prop="sendMethod">
|
||||
<el-select class="selectClass" v-model="dialogData.rowData!.sendMethod" multiple cleanable>
|
||||
<el-option value="mail" :label="$t('xpack.alert.mail')" />
|
||||
<el-option v-if="!isProductPro" value="bark" :label="$t('xpack.alert.bark')" />
|
||||
<el-select
|
||||
class="selectClass"
|
||||
popper-class="alert-config-method-dropdown"
|
||||
v-model="dialogData.rowData!.sendMethod"
|
||||
multiple
|
||||
cleanable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="3"
|
||||
@change="handleSendMethodChange"
|
||||
>
|
||||
<el-option :value="ALL_SEND_METHOD" :label="$t('commons.table.all')">
|
||||
<div class="alert-config-option alert-config-option--all">
|
||||
<span class="alert-config-option__name">
|
||||
{{ $t('commons.table.all') }}
|
||||
</span>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option
|
||||
value="weCom"
|
||||
v-if="!isIntl"
|
||||
:disabled="!isProductPro"
|
||||
:label="$t('xpack.alert.weCom')"
|
||||
/>
|
||||
<el-option
|
||||
value="dingTalk"
|
||||
v-if="!isIntl"
|
||||
:disabled="!isProductPro"
|
||||
:label="$t('xpack.alert.dingTalk')"
|
||||
/>
|
||||
<el-option
|
||||
value="feiShu"
|
||||
v-if="!isIntl"
|
||||
:disabled="!isProductPro"
|
||||
:label="$t('xpack.alert.feiShu')"
|
||||
/>
|
||||
<el-option v-if="isProductPro" value="bark" :label="$t('xpack.alert.bark')" />
|
||||
<el-option
|
||||
value="sms"
|
||||
v-if="!isIntl || !isEE"
|
||||
:disabled="!isProductPro"
|
||||
:label="$t('xpack.alert.sms')"
|
||||
/>
|
||||
v-for="opt in configOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:label="opt.label"
|
||||
:disabled="opt.disabled"
|
||||
>
|
||||
<div class="alert-config-option">
|
||||
<span class="alert-config-option__name" :title="opt.label">
|
||||
{{ opt.label }}
|
||||
</span>
|
||||
<el-tag class="alert-config-option__tag" effect="light" size="small" round>
|
||||
{{ opt.typeLabel }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<span class="input-help">
|
||||
@@ -356,6 +362,10 @@
|
||||
: ''
|
||||
}}
|
||||
</span>
|
||||
|
||||
<el-form-item v-if="dialogData.title === 'edit' && isEE" :label="$t('commons.table.updater')">
|
||||
<el-input :model-value="dialogData.rowData?.updateUser || '-'" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
@@ -376,11 +386,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { reactive, ref, onMounted, computed } from 'vue';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { Alert } from '@/api/interface/alert';
|
||||
import { listSSL, listWebsites } from '@/api/modules/website';
|
||||
import { CreateAlert, ListDisks, UpdateAlert, ListClams, ListCronJob } from '@/api/modules/alert';
|
||||
import { CreateAlert, ListDisks, UpdateAlert, ListClams, ListCronJob, PageAlertConfigs } from '@/api/modules/alert';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
@@ -390,6 +400,95 @@ import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMaster, isProductPro, isIntl, isEE } = useGlobalStore();
|
||||
|
||||
const alertConfigs = ref<Alert.AlertConfigInfo[]>([]);
|
||||
const loadAlertConfigs = async () => {
|
||||
try {
|
||||
const res = await PageAlertConfigs({ page: 1, pageSize: 1000 });
|
||||
alertConfigs.value = res.data?.items || [];
|
||||
} catch {}
|
||||
};
|
||||
onMounted(() => {
|
||||
loadAlertConfigs();
|
||||
});
|
||||
|
||||
const ALL_SEND_METHOD = '__all__';
|
||||
|
||||
const configOptions = computed(() => {
|
||||
const hiddenTypes: string[] = [];
|
||||
if (isIntl.value || isEE.value) {
|
||||
hiddenTypes.push('sms');
|
||||
}
|
||||
return alertConfigs.value
|
||||
.filter((c) => c.type !== 'common' && !hiddenTypes.includes(c.type))
|
||||
.map((c) => ({
|
||||
value: String(c.id),
|
||||
label: getConfigOptionLabel(c),
|
||||
typeLabel: getConfigTypeLabel(c.type),
|
||||
type: c.type,
|
||||
disabled: c.status !== 'Enable',
|
||||
}));
|
||||
});
|
||||
|
||||
const allConfigValues = computed(() => configOptions.value.filter((c) => !c.disabled).map((c) => c.value));
|
||||
|
||||
const legacyMethodTypeMap: Record<string, string> = {
|
||||
mail: 'email',
|
||||
email: 'email',
|
||||
sms: 'sms',
|
||||
bark: 'bark',
|
||||
weCom: 'weCom',
|
||||
dingTalk: 'dingTalk',
|
||||
feiShu: 'feiShu',
|
||||
};
|
||||
|
||||
const normalizeMethodValues = (methods: string[]) => {
|
||||
return methods.map((method) => {
|
||||
if (/^\d+$/.test(method)) return method;
|
||||
const configType = legacyMethodTypeMap[method];
|
||||
const matched = configOptions.value.find((item) => item.type === configType);
|
||||
return matched?.value || method;
|
||||
});
|
||||
};
|
||||
|
||||
const getConfigTypeLabel = (type: string): string => {
|
||||
return i18n.global.t(`xpack.alert.${type}`);
|
||||
};
|
||||
|
||||
const getConfigOptionLabel = (c: Alert.AlertConfigInfo): string => {
|
||||
try {
|
||||
const cfg = JSON.parse(c.config || '{}');
|
||||
const name = cfg.displayName || cfg.sender || cfg.phone || '';
|
||||
if (c.type === 'email') {
|
||||
return name ? `${name}` : cfg.sender || getConfigTypeLabel(c.type);
|
||||
}
|
||||
if (cfg.webhooks && cfg.webhooks.length > 0) {
|
||||
return cfg.webhooks
|
||||
.map((w: { displayName: string }) => w.displayName || '')
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
}
|
||||
if (cfg.displayName) {
|
||||
return cfg.displayName;
|
||||
}
|
||||
if (c.type === 'sms') {
|
||||
return cfg.phone || getConfigTypeLabel(c.type);
|
||||
}
|
||||
} catch {}
|
||||
return getConfigTypeLabel(c.type);
|
||||
};
|
||||
|
||||
const handleSendMethodChange = (values: string[]) => {
|
||||
if (!dialogData.value.rowData) return;
|
||||
if (
|
||||
allConfigValues.value.length > 0 &&
|
||||
(values.includes(ALL_SEND_METHOD) || allConfigValues.value.every((item) => values.includes(item)))
|
||||
) {
|
||||
dialogData.value.rowData.sendMethod = [ALL_SEND_METHOD];
|
||||
return;
|
||||
}
|
||||
dialogData.value.rowData.sendMethod = values.filter((item) => item !== ALL_SEND_METHOD);
|
||||
};
|
||||
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
rowData?: Alert.AlertInfo;
|
||||
@@ -432,7 +531,16 @@ const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
dialogData.value.rowData.sendMethod = [];
|
||||
if (dialogData.value.rowData.method != '') {
|
||||
dialogData.value.rowData.sendMethod = dialogData.value.rowData.method.split(',');
|
||||
const sendMethods = normalizeMethodValues(dialogData.value.rowData.method.split(',').filter(Boolean));
|
||||
if (
|
||||
sendMethods.length > 0 &&
|
||||
allConfigValues.value.length > 0 &&
|
||||
allConfigValues.value.every((item) => sendMethods.includes(item))
|
||||
) {
|
||||
dialogData.value.rowData.sendMethod = [ALL_SEND_METHOD];
|
||||
} else {
|
||||
dialogData.value.rowData.sendMethod = sendMethods;
|
||||
}
|
||||
}
|
||||
if (cronjobTypes.includes(dialogData.value.rowData.type)) {
|
||||
dialogData.value.rowData.subType = dialogData.value.rowData.type;
|
||||
@@ -457,7 +565,11 @@ const allTaskOptions = [
|
||||
{ value: 'panelPwdEndTime', label: 'xpack.alert.panelPwdEndTime', show: isMaster.value && !isEE.value },
|
||||
{ value: 'sshLogin', label: 'xpack.alert.sshLogin', show: true },
|
||||
{ value: 'panelLogin', label: 'xpack.alert.panelLogin', show: isMaster.value },
|
||||
{ value: 'licenseException', label: 'xpack.alert.licenseException', show: isMaster.value && isProductPro.value },
|
||||
{
|
||||
value: 'licenseException',
|
||||
label: 'xpack.alert.licenseException',
|
||||
show: isMaster.value && isProductPro.value && !isEE.value,
|
||||
},
|
||||
{ value: 'ssl', label: 'xpack.alert.ssl', show: true },
|
||||
{ value: 'siteEndTime', label: 'xpack.alert.siteEndTime', show: true },
|
||||
{
|
||||
@@ -789,7 +901,10 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (!dialogData.value.rowData) return;
|
||||
dialogData.value.rowData.method = dialogData.value.rowData.sendMethod.join(',');
|
||||
const sendMethods = dialogData.value.rowData.sendMethod.includes(ALL_SEND_METHOD)
|
||||
? allConfigValues.value
|
||||
: dialogData.value.rowData.sendMethod;
|
||||
dialogData.value.rowData.method = sendMethods.join(',');
|
||||
dialogData.value.rowData.title = formatTitle(dialogData.value.rowData);
|
||||
if (dialogData.value.rowData.type === 'cronJob') {
|
||||
dialogData.value.rowData.type = dialogData.value.rowData.subType;
|
||||
@@ -832,4 +947,37 @@ defineExpose({
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.alert-config-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.alert-config-option__name {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.alert-config-option__tag {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.alert-config-option--all {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
:global(.alert-config-method-dropdown .el-select-dropdown__item) {
|
||||
padding-right: 52px;
|
||||
}
|
||||
|
||||
:global(.alert-config-method-dropdown .el-select-dropdown__item.is-selected::after) {
|
||||
right: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,12 +21,11 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="$t('xpack.alert.alertMethod')" prop="method" width="150px">
|
||||
<el-table-column :label="$t('xpack.alert.alertMethod')" prop="method" width="200px">
|
||||
<template #default="{ row }">
|
||||
{{ formatMethod(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
:label="$t('commons.table.status')"
|
||||
fix
|
||||
@@ -90,6 +89,7 @@ import {
|
||||
SyncAlertAll,
|
||||
SyncOfflineAlert,
|
||||
ListAlertConfigs,
|
||||
PageAlertConfigs,
|
||||
} from '@/api/modules/alert';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
@@ -99,6 +99,18 @@ const { t } = i18n.global;
|
||||
const loading = ref(false);
|
||||
const data = ref();
|
||||
const isOffline = ref('Disable');
|
||||
const configMap = ref<Map<string, Alert.AlertConfigInfo>>(new Map());
|
||||
|
||||
const loadConfigMap = async () => {
|
||||
try {
|
||||
const res = await PageAlertConfigs({ page: 1, pageSize: 1000 });
|
||||
const map = new Map<string, Alert.AlertConfigInfo>();
|
||||
for (const c of res.data?.items || []) {
|
||||
map.set(String(c.id), c);
|
||||
}
|
||||
configMap.value = map;
|
||||
} catch {}
|
||||
};
|
||||
const resourceTypes = [
|
||||
'cpu',
|
||||
'memory',
|
||||
@@ -135,10 +147,14 @@ const buttons = [
|
||||
syncAlert(row);
|
||||
},
|
||||
disabled: (row: Alert.AlertLog) => {
|
||||
return (
|
||||
(row.method != 'sms' && row.status != 'PushSuccess' && row.status != 'SyncError') ||
|
||||
row.status == 'Success'
|
||||
);
|
||||
const isSms = row.method
|
||||
.split(',')
|
||||
.filter(Boolean)
|
||||
.some((item) => {
|
||||
const config = configMap.value.get(item.trim());
|
||||
return config ? config.type === 'sms' : item.trim() === 'sms';
|
||||
});
|
||||
return (!isSms && row.status != 'PushSuccess' && row.status != 'SyncError') || row.status == 'Success';
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -213,26 +229,50 @@ const formatMessage = (row: Alert.AlertInfo) => {
|
||||
};
|
||||
|
||||
const formatMethod = (row: Alert.AlertLog) => {
|
||||
switch (row.method) {
|
||||
case 'mail':
|
||||
return t('xpack.alert.mail');
|
||||
case 'dingTalk':
|
||||
return t('xpack.alert.dingTalk');
|
||||
case 'weCom':
|
||||
return t('xpack.alert.weCom');
|
||||
case 'feiShu':
|
||||
return t('xpack.alert.feiShu');
|
||||
case 'wechat':
|
||||
return t('xpack.alert.wechat');
|
||||
case 'sms':
|
||||
return t('xpack.alert.sms');
|
||||
case 'webhook':
|
||||
return t('xpack.alert.webhook');
|
||||
case 'bark':
|
||||
return t('xpack.alert.bark');
|
||||
default:
|
||||
return t('xpack.alert.unknown');
|
||||
}
|
||||
if (!row.method) return '-';
|
||||
|
||||
const formatMethodPart = (method: string) => {
|
||||
const config = configMap.value.get(method);
|
||||
if (config) {
|
||||
const typeKey = config.type === 'email' ? 'mail' : config.type;
|
||||
const typeLabel = i18n.global.t('xpack.alert.' + typeKey);
|
||||
try {
|
||||
const cfg = JSON.parse(config.config || '{}');
|
||||
const name = cfg.displayName || cfg.sender || cfg.phone || '';
|
||||
return name ? `${name}(${typeLabel})` : typeLabel;
|
||||
} catch {
|
||||
return typeLabel;
|
||||
}
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case 'mail':
|
||||
case 'email':
|
||||
return t('xpack.alert.mail');
|
||||
case 'dingTalk':
|
||||
return t('xpack.alert.dingTalk');
|
||||
case 'weCom':
|
||||
return t('xpack.alert.weCom');
|
||||
case 'feiShu':
|
||||
return t('xpack.alert.feiShu');
|
||||
case 'wechat':
|
||||
return t('xpack.alert.wechat');
|
||||
case 'sms':
|
||||
return t('xpack.alert.sms');
|
||||
case 'webhook':
|
||||
return t('xpack.alert.webhook');
|
||||
case 'bark':
|
||||
return t('xpack.alert.bark');
|
||||
default:
|
||||
return /^\d+$/.test(method) ? `#${method}` : method;
|
||||
}
|
||||
};
|
||||
|
||||
return `「${row.method
|
||||
.split(',')
|
||||
.filter(Boolean)
|
||||
.map((item) => formatMethodPart(item.trim()))
|
||||
.join('|')}」`;
|
||||
};
|
||||
|
||||
const formatCount = (row: Alert.AlertInfo) => {
|
||||
@@ -253,6 +293,7 @@ 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;
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<DrawerPro v-model="drawerVisible" :header="drawerHeader" @close="handleClose" size="736">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:rules="currentRules"
|
||||
label-position="top"
|
||||
:model="form"
|
||||
@submit.prevent
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item v-if="!isEdit" :label="$t('commons.table.type')" prop="type">
|
||||
<el-select v-model="form.type" class="w-full" @change="onTypeChange">
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="form.type === 'email'">
|
||||
<el-form-item :label="$t('xpack.alert.displayName')" prop="config.displayName">
|
||||
<el-input v-model="form.config.displayName" />
|
||||
<span class="input-help">{{ $t('xpack.alert.displayNameHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.sender')" prop="config.sender">
|
||||
<el-input v-model.trim="form.config.sender" />
|
||||
<span class="input-help">{{ $t('xpack.alert.senderHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.username')" prop="config.userName">
|
||||
<el-input v-model.trim="form.config.userName" />
|
||||
<span class="input-help">{{ $t('xpack.alert.userNameHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.password')" prop="config.password">
|
||||
<el-input v-model.trim="form.config.password" type="password" show-password />
|
||||
<span class="input-help">{{ $t('xpack.alert.passwordHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.host')" prop="config.host">
|
||||
<el-input v-model.trim="form.config.host" placeholder="smtp.qq.com" />
|
||||
<span class="input-help">{{ $t('xpack.alert.hostHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.port')" prop="config.port">
|
||||
<el-input v-model.number="form.config.port" :min="1" :max="65535" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.encryption')" prop="config.encryption">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="el-form-item__label">SSL</span>
|
||||
<el-switch
|
||||
v-permission
|
||||
v-model="form.config.encryption"
|
||||
:active-value="'SSL'"
|
||||
:inactive-value="form.config.encryption === 'SSL' ? 'NONE' : form.config.encryption"
|
||||
/>
|
||||
</div>
|
||||
<span class="input-help">{{ $t('xpack.alert.sslHelper') }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="el-form-item__label">TLS</span>
|
||||
<el-switch
|
||||
v-permission
|
||||
v-model="form.config.encryption"
|
||||
:active-value="'TLS'"
|
||||
:inactive-value="form.config.encryption === 'TLS' ? 'NONE' : form.config.encryption"
|
||||
/>
|
||||
</div>
|
||||
<span class="input-help">{{ $t('xpack.alert.tlsHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.recipient')" prop="recipient">
|
||||
<el-input v-model.trim="form.recipient" placeholder="a@example.com,b@example.com" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form.type === 'sms'">
|
||||
<el-form-item :label="$t('xpack.alert.phone')" prop="smsPhone" :rules="[Rules.phone]">
|
||||
<el-input clearable v-model="form.smsPhone" />
|
||||
<span class="input-help">{{ $t('xpack.alert.phoneHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('xpack.alert.dailyAlertNum')"
|
||||
prop="smsDailyAlertNum"
|
||||
:rules="[Rules.integerNumber, checkNumberRange(20, 100)]"
|
||||
>
|
||||
<el-input clearable v-model.number="form.smsDailyAlertNum" min="20" max="100" />
|
||||
<span class="input-help">{{ $t('xpack.alert.dailyAlertNumHelper') }}</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-form-item :label="$t('xpack.alert.webhookName')" prop="webhookName">
|
||||
<el-input v-model="form.webhookName" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.webhookUrl')" prop="webhookUrl">
|
||||
<el-input v-model.trim="form.webhookUrl" :rows="2" type="password" show-password />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item v-if="isEdit && isEE" :label="$t('commons.table.updater')">
|
||||
<el-input :model-value="form.updateUser || '-'" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div v-if="form.type === 'email'" class="flex items-center justify-between">
|
||||
<el-button v-permission @click="onTest(formRef)" plain type="primary">
|
||||
{{ $t('xpack.alert.test') }}
|
||||
</el-button>
|
||||
<div>
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button
|
||||
v-permission
|
||||
:disabled="loading || (form.type === 'email' && !isOK)"
|
||||
type="primary"
|
||||
@click="onSave(formRef)"
|
||||
>
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex justify-end gap-2">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button v-permission :disabled="loading" type="primary" @click="onSave(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { PageAlertConfigs, TestAlertConfig, UpdateAlertConfig } from '@/api/modules/alert';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { Alert } from '@/api/interface/alert';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const { isProductPro, isIntl, isEE } = useGlobalStore();
|
||||
|
||||
const emailRules = {
|
||||
'config.displayName': [Rules.requiredInput, { validator: checkDisplayNameDuplicate, trigger: 'blur' }],
|
||||
'config.sender': [Rules.requiredInput],
|
||||
'config.host': [Rules.requiredInput],
|
||||
'config.port': [Rules.requiredInput],
|
||||
recipient: [Rules.requiredInput],
|
||||
};
|
||||
|
||||
const webhookRules = {
|
||||
webhookName: [Rules.requiredInput, { validator: checkDisplayNameDuplicate, trigger: 'blur' }],
|
||||
webhookUrl: [Rules.requiredInput],
|
||||
};
|
||||
|
||||
const currentRules = computed(() => {
|
||||
if (form.type === 'email') return emailRules;
|
||||
if (form.type === 'sms') return {};
|
||||
return webhookRules;
|
||||
});
|
||||
|
||||
const typeOptions = computed(() => {
|
||||
const options: { value: string; label: string }[] = [{ value: 'email', label: i18n.global.t('xpack.alert.mail') }];
|
||||
if (isProductPro.value && !isIntl.value) {
|
||||
options.push({ value: 'weCom', label: i18n.global.t('xpack.alert.weCom') });
|
||||
options.push({ value: 'dingTalk', label: i18n.global.t('xpack.alert.dingTalk') });
|
||||
options.push({ value: 'feiShu', label: i18n.global.t('xpack.alert.feiShu') });
|
||||
}
|
||||
options.push({ value: 'bark', label: i18n.global.t('xpack.alert.bark') });
|
||||
if (!isEE.value && !isIntl.value) {
|
||||
options.push({ value: 'sms', label: i18n.global.t('xpack.alert.sms') });
|
||||
}
|
||||
return options;
|
||||
});
|
||||
|
||||
const defaultEmailForm = {
|
||||
displayName: '',
|
||||
sender: '',
|
||||
userName: '',
|
||||
password: '',
|
||||
host: '',
|
||||
port: 465,
|
||||
encryption: 'NONE',
|
||||
status: 'Enable',
|
||||
recipient: '',
|
||||
};
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const loading = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const isOK = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const alertConfigs = ref<Alert.AlertConfigInfo[]>([]);
|
||||
|
||||
const loadAlertConfigs = async () => {
|
||||
try {
|
||||
const res = await PageAlertConfigs({ page: 1, pageSize: 1000 });
|
||||
alertConfigs.value = res.data?.items?.filter((item: Alert.AlertConfigInfo) => item.type !== 'common') || [];
|
||||
} catch {
|
||||
alertConfigs.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const form = reactive({
|
||||
id: undefined as number | undefined,
|
||||
type: 'email',
|
||||
title: '',
|
||||
status: 'Enable',
|
||||
updateUser: '',
|
||||
config: { ...defaultEmailForm } as Record<string, any>,
|
||||
recipient: '',
|
||||
webhookName: '',
|
||||
webhookUrl: '',
|
||||
smsPhone: '',
|
||||
smsDailyAlertNum: 50,
|
||||
});
|
||||
|
||||
const drawerHeader = computed(() => {
|
||||
if (isEdit.value) {
|
||||
return i18n.global.t('xpack.alert.' + form.type);
|
||||
}
|
||||
return i18n.global.t('commons.button.create');
|
||||
});
|
||||
|
||||
const normalizeDisplayName = (value?: string) => value?.trim() || '';
|
||||
|
||||
function checkDisplayNameDuplicate(_rule: unknown, value: string, callback: (error?: Error) => void) {
|
||||
const currentValue = normalizeDisplayName(value);
|
||||
if (!currentValue) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicated = alertConfigs.value.some((item) => {
|
||||
if (item.type !== form.type) {
|
||||
return false;
|
||||
}
|
||||
if (form.id && item.id === form.id) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const config = JSON.parse(item.config || '{}') as { displayName?: string };
|
||||
return normalizeDisplayName(config.displayName) === currentValue;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (duplicated) {
|
||||
callback(new Error(i18n.global.t('commons.rule.duplicate')));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
const titleMap: Record<string, string> = {
|
||||
email: 'xpack.alert.emailConfig',
|
||||
weCom: 'xpack.alert.weCom',
|
||||
dingTalk: 'xpack.alert.dingTalk',
|
||||
feiShu: 'xpack.alert.feiShu',
|
||||
bark: 'xpack.alert.bark',
|
||||
sms: 'xpack.alert.smsConfig',
|
||||
};
|
||||
|
||||
interface DrawerProps {
|
||||
id?: number;
|
||||
type?: string;
|
||||
config?: Record<string, any>;
|
||||
status?: string;
|
||||
updateUser?: string;
|
||||
}
|
||||
|
||||
const acceptParams = (params: DrawerProps): void => {
|
||||
if (params.id && params.id > 0) {
|
||||
isEdit.value = true;
|
||||
form.id = params.id;
|
||||
form.type = params.type || 'email';
|
||||
form.title = titleMap[form.type] || '';
|
||||
form.status = params.status || 'Enable';
|
||||
form.updateUser = params.updateUser || '';
|
||||
|
||||
if (form.type === 'email') {
|
||||
form.config = { ...defaultEmailForm, ...(params.config || {}) };
|
||||
form.recipient = params.config?.recipient || '';
|
||||
} else if (form.type === 'sms') {
|
||||
form.smsPhone = params.config?.phone || '';
|
||||
form.smsDailyAlertNum = params.config?.alertDailyNum || 50;
|
||||
} else {
|
||||
form.webhookName = params.config?.displayName || '';
|
||||
form.webhookUrl = params.config?.url || '';
|
||||
}
|
||||
} else {
|
||||
isEdit.value = false;
|
||||
form.id = undefined;
|
||||
form.type = params.type || 'email';
|
||||
form.title = titleMap[form.type] || '';
|
||||
form.status = 'Enable';
|
||||
form.updateUser = '';
|
||||
form.config = { ...defaultEmailForm };
|
||||
form.recipient = '';
|
||||
form.webhookName = '';
|
||||
form.webhookUrl = '';
|
||||
form.smsPhone = '';
|
||||
form.smsDailyAlertNum = 50;
|
||||
}
|
||||
|
||||
isOK.value = false;
|
||||
drawerVisible.value = true;
|
||||
void loadAlertConfigs();
|
||||
};
|
||||
|
||||
const onTypeChange = (type: string) => {
|
||||
form.title = titleMap[type] || '';
|
||||
if (type === 'email') {
|
||||
form.config = { ...defaultEmailForm };
|
||||
form.recipient = '';
|
||||
} else if (type === 'sms') {
|
||||
form.smsPhone = '';
|
||||
form.smsDailyAlertNum = 50;
|
||||
} else {
|
||||
form.config = {};
|
||||
form.webhookName = '';
|
||||
form.webhookUrl = '';
|
||||
}
|
||||
isOK.value = false;
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
const buildSavePayload = () => {
|
||||
if (form.type === 'email') {
|
||||
const configInfo = {
|
||||
...form.config,
|
||||
recipient: form.recipient,
|
||||
};
|
||||
return {
|
||||
id: form.id,
|
||||
type: 'email',
|
||||
title: titleMap['email'],
|
||||
status: form.status,
|
||||
config: JSON.stringify(configInfo),
|
||||
};
|
||||
}
|
||||
if (form.type === 'sms') {
|
||||
const configInfo = { phone: form.smsPhone, alertDailyNum: form.smsDailyAlertNum };
|
||||
return {
|
||||
id: form.id,
|
||||
type: 'sms',
|
||||
title: titleMap['sms'],
|
||||
status: form.status,
|
||||
config: JSON.stringify(configInfo),
|
||||
};
|
||||
}
|
||||
const configInfo = {
|
||||
displayName: form.webhookName,
|
||||
url: form.webhookUrl,
|
||||
};
|
||||
return {
|
||||
id: form.id,
|
||||
type: form.type,
|
||||
title: form.title,
|
||||
status: form.status,
|
||||
config: JSON.stringify(configInfo),
|
||||
};
|
||||
};
|
||||
|
||||
const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await UpdateAlertConfig(buildSavePayload());
|
||||
loading.value = false;
|
||||
void loadAlertConfigs();
|
||||
handleClose();
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
} catch (error) {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onTest = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl || form.type !== 'email') return;
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const testConfig = {
|
||||
...form.config,
|
||||
recipient: form.recipient,
|
||||
};
|
||||
const res = await TestAlertConfig(testConfig);
|
||||
loading.value = false;
|
||||
if (res.data) {
|
||||
isOK.value = true;
|
||||
MsgSuccess(i18n.global.t('xpack.alert.alertTestOk'));
|
||||
} else {
|
||||
MsgError(i18n.global.t('xpack.alert.alertTestFailed'));
|
||||
}
|
||||
} catch {
|
||||
loading.value = false;
|
||||
MsgError(i18n.global.t('xpack.alert.alertTestFailed'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => form.config,
|
||||
() => {
|
||||
if (form.type === 'email') {
|
||||
isOK.value = false;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => form.recipient,
|
||||
() => {
|
||||
if (form.type === 'email') {
|
||||
isOK.value = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
isOK.value = false;
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
void loadAlertConfigs();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
@@ -69,8 +69,33 @@
|
||||
{{ $t('xpack.alert.tlsHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('xpack.alert.recipient')" prop="recipient">
|
||||
<el-input v-model="form.config.recipient" />
|
||||
<el-form-item :label="$t('xpack.alert.recipient')" prop="recipients">
|
||||
<div class="w-full">
|
||||
<div
|
||||
v-for="(item, index) in form.config.recipients"
|
||||
:key="index"
|
||||
class="flex items-center mb-2 gap-2"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.config.recipients[index]"
|
||||
:placeholder="$t('xpack.alert.recipientPlaceholder')"
|
||||
/>
|
||||
<el-button
|
||||
v-permission
|
||||
type="danger"
|
||||
plain
|
||||
circle
|
||||
@click="removeRecipient(index)"
|
||||
:disabled="form.config.recipients.length <= 1"
|
||||
>
|
||||
<el-icon><Minus /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button v-permission type="primary" plain @click="addRecipient">
|
||||
<el-icon><Plus /></el-icon>
|
||||
{{ $t('xpack.alert.addRecipient') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -97,6 +122,7 @@ import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { TestAlertConfig, UpdateAlertConfig } from '@/api/modules/alert';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { Plus, Minus } from '@element-plus/icons-vue';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
@@ -105,7 +131,23 @@ const rules = {
|
||||
sender: [Rules.requiredInput],
|
||||
host: [Rules.requiredInput],
|
||||
port: [Rules.requiredInput],
|
||||
recipient: [Rules.requiredInput],
|
||||
recipients: [
|
||||
{
|
||||
validator: (rule: any, value: string[], callback: any) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
|
||||
} else {
|
||||
const hasEmpty = value.some((item) => !item || !item.trim());
|
||||
if (hasEmpty) {
|
||||
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
};
|
||||
interface Config {
|
||||
status: string;
|
||||
@@ -116,7 +158,8 @@ interface Config {
|
||||
host: string;
|
||||
port: number;
|
||||
encryption: string;
|
||||
recipient: string;
|
||||
recipient?: string;
|
||||
recipients?: string[];
|
||||
}
|
||||
interface DialogProps {
|
||||
id: number;
|
||||
@@ -126,7 +169,7 @@ const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
|
||||
const form = reactive({
|
||||
id: undefined,
|
||||
id: undefined as number | undefined,
|
||||
config: {
|
||||
displayName: '',
|
||||
sender: '',
|
||||
@@ -136,7 +179,7 @@ const form = reactive({
|
||||
port: 465,
|
||||
encryption: 'NONE',
|
||||
status: 'Enable',
|
||||
recipient: '',
|
||||
recipients: [''] as string[],
|
||||
},
|
||||
});
|
||||
const isOK = ref(false);
|
||||
@@ -144,10 +187,39 @@ const formRef = ref<FormInstance>();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
form.id = params.id;
|
||||
form.config = params.config;
|
||||
form.config.displayName = params.config.displayName || '';
|
||||
form.config.sender = params.config.sender || '';
|
||||
form.config.password = params.config.password || '';
|
||||
form.config.userName = params.config.userName || '';
|
||||
form.config.host = params.config.host || '';
|
||||
form.config.port = params.config.port || 465;
|
||||
form.config.encryption = params.config.encryption || 'NONE';
|
||||
form.config.status = params.config.status || 'Enable';
|
||||
|
||||
if (params.config.recipients && params.config.recipients.length > 0) {
|
||||
form.config.recipients = [...params.config.recipients];
|
||||
} else if (params.config.recipient) {
|
||||
form.config.recipients = params.config.recipient
|
||||
.split(',')
|
||||
.map((r) => r.trim())
|
||||
.filter((r) => r);
|
||||
} else {
|
||||
form.config.recipients = [''];
|
||||
}
|
||||
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
const addRecipient = () => {
|
||||
form.config.recipients.push('');
|
||||
};
|
||||
|
||||
const removeRecipient = (index: number) => {
|
||||
if (form.config.recipients.length > 1) {
|
||||
form.config.recipients.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
@@ -155,7 +227,10 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
form.config.status = 'Enable';
|
||||
const configInfo = form.config;
|
||||
const configInfo = {
|
||||
...form.config,
|
||||
recipient: form.config.recipients.filter((r) => r && r.trim()).join(','),
|
||||
};
|
||||
await UpdateAlertConfig({
|
||||
id: form.id,
|
||||
type: 'email',
|
||||
@@ -180,7 +255,11 @@ const onTest = async (formEl: FormInstance | undefined) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await TestAlertConfig(form.config)
|
||||
const testConfig = {
|
||||
...form.config,
|
||||
recipient: form.config.recipients.filter((r) => r && r.trim()).join(','),
|
||||
};
|
||||
await TestAlertConfig(testConfig)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.data) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,7 @@ interface DialogProps {
|
||||
phone: string;
|
||||
dailyAlertNum: number;
|
||||
id: number;
|
||||
status?: string;
|
||||
}
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
@@ -49,6 +50,7 @@ const form = reactive({
|
||||
phone: '',
|
||||
dailyAlertNum: 50,
|
||||
id: undefined,
|
||||
status: 'Enable',
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
@@ -57,6 +59,7 @@ const acceptParams = (params: DialogProps): void => {
|
||||
form.phone = params.phone;
|
||||
form.id = params.id;
|
||||
form.dailyAlertNum = Number(params.dailyAlertNum);
|
||||
form.status = params.status || 'Enable';
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
@@ -71,7 +74,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
id: form.id,
|
||||
type: 'sms',
|
||||
title: 'xpack.alert.smsConfig',
|
||||
status: 'Enable',
|
||||
status: form.status,
|
||||
config: JSON.stringify(configInfo),
|
||||
});
|
||||
|
||||
|
||||
@@ -153,37 +153,37 @@
|
||||
>
|
||||
<el-select
|
||||
class="selectClass"
|
||||
popper-class="alert-config-method-dropdown"
|
||||
v-model="dialogData.rowData!.alertMethodItems"
|
||||
multiple
|
||||
cleanable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="3"
|
||||
>
|
||||
<el-option value="mail" :label="$t('xpack.alert.mail')" />
|
||||
<el-option v-if="!isProductPro" value="bark" :label="$t('xpack.alert.bark')" />
|
||||
<el-option
|
||||
value="weCom"
|
||||
v-if="!isIntl"
|
||||
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.weCom')"
|
||||
/>
|
||||
<el-option
|
||||
value="dingTalk"
|
||||
v-if="!isIntl"
|
||||
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.dingTalk')"
|
||||
/>
|
||||
<el-option
|
||||
value="feiShu"
|
||||
v-if="!isIntl"
|
||||
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.feiShu')"
|
||||
/>
|
||||
<el-option v-if="isProductPro" value="bark" :label="$t('xpack.alert.bark')" />
|
||||
<el-option
|
||||
value="sms"
|
||||
v-if="!isIntl || !isEE"
|
||||
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
|
||||
:label="$t('xpack.alert.sms')"
|
||||
/>
|
||||
<el-option-group
|
||||
v-for="group in groupedAlertConfigOptions"
|
||||
:key="group.type"
|
||||
:label="
|
||||
i18n.global.t('xpack.alert.' + (group.type === 'email' ? 'mail' : group.type))
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in group.options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:label="opt.label"
|
||||
>
|
||||
<div class="alert-config-option">
|
||||
<span class="alert-config-option__name" :title="opt.label">
|
||||
{{ opt.label }}
|
||||
</span>
|
||||
<el-tag class="alert-config-option__tag" effect="light" size="small" round>
|
||||
{{ opt.typeLabel }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -233,7 +233,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { reactive, ref, computed, onMounted } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import i18n from '@/lang';
|
||||
@@ -243,9 +243,93 @@ import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Toolbox } from '@/api/interface/toolbox';
|
||||
import { createClam, updateClam } from '@/api/modules/toolbox';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { Alert } from '@/api/interface/alert';
|
||||
import { ListAlertConfigs } from '@/api/modules/alert';
|
||||
import { specOptions, transObjToSpec, transSpecToObj, weekOptions } from '@/views/cronjob/cronjob/helper';
|
||||
import { splitTimeFromSecond, transferTimeToSecond } from '@/utils/validate';
|
||||
const { isIntl, isProductPro, isEE } = useGlobalStore();
|
||||
|
||||
const alertConfigs = ref<Alert.AlertConfigInfo[]>([]);
|
||||
const loadAlertConfigs = async () => {
|
||||
try {
|
||||
const res = await ListAlertConfigs();
|
||||
alertConfigs.value = res.data || [];
|
||||
} catch {}
|
||||
};
|
||||
onMounted(() => {
|
||||
loadAlertConfigs();
|
||||
});
|
||||
|
||||
const alertConfigOptions = computed(() => {
|
||||
const hiddenTypes: string[] = [];
|
||||
if (isIntl.value || isEE.value) hiddenTypes.push('sms');
|
||||
return alertConfigs.value
|
||||
.filter((c) => c.status === 'Enable' && c.type !== 'common' && !hiddenTypes.includes(c.type))
|
||||
.map((c) => ({
|
||||
value: String(c.id),
|
||||
label: getAlertConfigOptionLabel(c),
|
||||
type: c.type,
|
||||
}));
|
||||
});
|
||||
|
||||
const legacyAlertMethodTypeMap: Record<string, string> = {
|
||||
mail: 'email',
|
||||
email: 'email',
|
||||
sms: 'sms',
|
||||
bark: 'bark',
|
||||
weCom: 'weCom',
|
||||
dingTalk: 'dingTalk',
|
||||
feiShu: 'feiShu',
|
||||
};
|
||||
|
||||
const normalizeAlertMethodItems = (methods: string[]) => {
|
||||
return methods.map((method) => {
|
||||
if (/^\d+$/.test(method)) return method;
|
||||
const configType = legacyAlertMethodTypeMap[method];
|
||||
const matched = alertConfigOptions.value.find((item) => item.type === configType);
|
||||
return matched?.value || method;
|
||||
});
|
||||
};
|
||||
|
||||
const groupedAlertConfigOptions = computed(() => {
|
||||
const typeMap = new Map<string, { value: string; label: string }[]>();
|
||||
for (const opt of alertConfigOptions.value) {
|
||||
if (!typeMap.has(opt.type)) typeMap.set(opt.type, []);
|
||||
typeMap.get(opt.type)!.push({ value: opt.value, label: opt.label });
|
||||
}
|
||||
const groups: {
|
||||
type: string;
|
||||
options: { value: string; label: string; typeLabel: string }[];
|
||||
}[] = [];
|
||||
const typeOrder = ['email', 'sms', 'weCom', 'dingTalk', 'feiShu', 'bark'];
|
||||
for (const t of typeOrder) {
|
||||
if (typeMap.has(t)) {
|
||||
const typeLabel = getConfigTypeLabel(t);
|
||||
groups.push({
|
||||
type: t,
|
||||
options: typeMap.get(t)!.map((item) => ({
|
||||
...item,
|
||||
typeLabel,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
|
||||
const getConfigTypeLabel = (type: string): string => {
|
||||
return i18n.global.t(`xpack.alert.${type === 'email' ? 'mail' : type}`);
|
||||
};
|
||||
|
||||
const getAlertConfigOptionLabel = (c: Alert.AlertConfigInfo): string => {
|
||||
try {
|
||||
const cfg = JSON.parse(c.config || '{}');
|
||||
return cfg.displayName || i18n.global.t(`xpack.alert.${c.type === 'email' ? 'mail' : c.type}`);
|
||||
} catch {
|
||||
return i18n.global.t(`xpack.alert.${c.type === 'email' ? 'mail' : c.type}`);
|
||||
}
|
||||
};
|
||||
|
||||
const licenseRef = ref();
|
||||
const scanDirRef = ref();
|
||||
const infectedDirRef = ref();
|
||||
@@ -284,7 +368,9 @@ const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
|
||||
dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
|
||||
if (dialogData.value.rowData!.alertMethod) {
|
||||
dialogData.value.rowData!.alertMethodItems = dialogData.value.rowData!.alertMethod.split(',') || [];
|
||||
dialogData.value.rowData!.alertMethodItems = normalizeAlertMethodItems(
|
||||
dialogData.value.rowData!.alertMethod.split(',') || [],
|
||||
);
|
||||
} else {
|
||||
dialogData.value.rowData!.alertMethodItems = [];
|
||||
}
|
||||
@@ -524,6 +610,35 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.alert-config-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.alert-config-option__name {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.alert-config-option__tag {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
:global(.alert-config-method-dropdown .el-select-dropdown__item) {
|
||||
padding-right: 52px;
|
||||
}
|
||||
|
||||
:global(.alert-config-method-dropdown .el-select-dropdown__item.is-selected::after) {
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.logText {
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user