1Panel/agent/utils/alert/alert.go
2025-07-23 08:58:24 +00:00

327 lines
9.7 KiB
Go

package alert
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/email"
"github.com/jinzhu/copier"
"net/http"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/global"
)
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) 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)
}
func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport) error {
var alertLog model.AlertLog
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
if err != nil {
return err
}
var cfg dto.AlertCommonConfig
err = json.Unmarshal([]byte(config.Config), &cfg)
if err != nil {
return err
}
create.Method = constant.Email
// 获取远端推送信息
if !global.IsMaster && cfg.IsOffline == constant.StatusEnable {
create.Status = constant.AlertPushing
return SaveAlertLog(create, &alertLog)
} else {
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 {
return err
}
smtpConfig := email.SMTPConfig{
Host: emailInfo.Host,
Port: emailInfo.Port,
Username: emailInfo.Sender,
Password: emailInfo.Password,
From: fmt.Sprintf("%s <%s>", emailInfo.DisplayName, emailInfo.Sender),
Encryption: emailInfo.Encryption,
Recipient: emailInfo.Recipient,
}
content := alert.Title
if GetEmailContent(alert.Type, params) != "" {
content = GetEmailContent(alert.Type, params)
}
msg := email.EmailMessage{
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
Body: content,
IsHTML: true,
}
if err = email.SendMail(smtpConfig, msg, transport); err != nil {
create.Message = err.Error()
create.Status = constant.AlertError
return SaveAlertLog(create, &alertLog)
}
create.Status = constant.AlertSuccess
return SaveAlertLog(create, &alertLog)
}
}
func SaveAlertLog(create dto.AlertLogCreate, alertLog *model.AlertLog) error {
alertRepo := repo.NewIAlertRepo()
if err := copier.Copy(&alertLog, &create); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
if err := alertRepo.CreateLog(alertLog); err != nil {
global.LOG.Errorf("Error creating alert logs, err: %v", err)
return err
}
return nil
}
func CreateNewAlertTask(quota, alertType, quotaType, method string) {
alertRepo := repo.NewIAlertRepo()
taskBase := model.AlertTask{
Type: alertType,
Quota: quota,
QuotaType: quotaType,
Method: method,
}
err := alertRepo.CreateAlertTask(&taskBase)
if err != nil {
global.LOG.Errorf("error creating alert tasks, err: %v", err)
}
}
func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param, method string) string {
alertDetail := dto.AlertDetail{
Type: GetCronJobType(alert.Type),
SubType: alert.Type,
Title: alert.Title,
Method: method,
Project: project,
Params: params,
}
marshal, err := json.Marshal(alertDetail)
if err != nil {
global.LOG.Errorf("error processing alert detail, err: %v", err)
return ""
}
return string(marshal)
}
func ProcessAlertRule(alert dto.AlertDTO) string {
marshal, err := json.Marshal(alert)
if err != nil {
global.LOG.Errorf("error processing alert rule, err: %v", err)
return ""
}
return string(marshal)
}
func GetCronJobType(alertType string) string {
for _, at := range cronJobAlertTypes {
if at == alertType {
return "cronJob"
}
}
return alertType
}
func GetCronJobTypeName(cronJobType string) string {
module := cronJobType
switch cronJobType {
case "shell":
module = "Shell 脚本"
case "app":
module = "备份应用"
case "website":
module = "备份网站"
case "database":
module = "备份数据库"
case "log":
module = "备份日志"
case "directory":
module = "备份目录"
case "curl":
module = "访问 URL"
case "cutWebsiteLog":
module = "切割网站日志"
case "clean":
module = "缓存清理"
case "snapshot":
module = "系统快照"
case "ntp":
module = "同步服务器时间"
default:
}
return module
}
func CreateAlertParams(param string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "param",
Value: param,
},
}
}
var checkTaskMutex sync.Mutex
func CheckTaskFrequency(method string) bool {
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
if err != nil {
return false
}
var cfg dto.AlertSmsConfig
err = json.Unmarshal([]byte(config.Config), &cfg)
if err != nil {
return false
}
limitCount := cfg.AlertDailyNum
checkTaskMutex.Lock()
todayCount, err := alertRepo.GetLicensePushCount(method)
defer checkTaskMutex.Unlock()
if err != nil {
global.LOG.Errorf("error getting license push count info, err: %v", err)
return false
}
if todayCount >= limitCount {
return false
}
return true
}
type Settings struct {
NoticeAlert Category `json:"noticeAlert"`
ResourceAlert Category `json:"resourceAlert"`
}
type Category struct {
SendTimeRange string `json:"sendTimeRange"`
Type []string `json:"type"`
}
// CheckSendTimeRange 是否在时间范围内
func CheckSendTimeRange(alertType string) bool {
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
if err != nil {
return false
}
var cfg dto.AlertCommonConfig
err = json.Unmarshal([]byte(config.Config), &cfg)
if err != nil {
return false
}
var timeRange string
if contains(cfg.AlertSendTimeRange.NoticeAlert.Type, alertType) {
timeRange = cfg.AlertSendTimeRange.NoticeAlert.SendTimeRange
} else if contains(cfg.AlertSendTimeRange.ResourceAlert.Type, alertType) {
timeRange = cfg.AlertSendTimeRange.ResourceAlert.SendTimeRange
} else {
global.LOG.Warnf("Alert type not found in sendTimeRange: %s", alertType)
return false
}
if !isWithinTimeRange(timeRange) {
return false
}
return true
}
func contains(arr []string, target string) bool {
for _, item := range arr {
if item == target {
return true
}
}
return false
}
func isWithinTimeRange(savedTimeString string) bool {
now := time.Now()
timeParts := strings.Split(savedTimeString, " - ")
if len(timeParts) != 2 {
global.LOG.Info("Time range string format error, should be: 'HH:MM:SS - HH:MM:SS'")
return false
}
startTime, err1 := time.Parse("15:04:05", strings.TrimSpace(timeParts[0]))
endTime, err2 := time.Parse("15:04:05", strings.TrimSpace(timeParts[1]))
if err1 != nil || err2 != nil {
global.LOG.Infof("Invalid time format in range: %s, errors: %v, %v", savedTimeString, err1, err2)
return false
}
skipTime := time.Date(now.Year(), now.Month(), now.Day(), startTime.Hour(), startTime.Minute(), startTime.Second(), 0, now.Location())
endSkipTime := time.Date(now.Year(), now.Month(), now.Day(), endTime.Hour(), endTime.Minute(), endTime.Second(), 0, now.Location())
if endSkipTime.Before(skipTime) {
return now.After(skipTime) || now.Before(endSkipTime)
}
return now.After(skipTime) && now.Before(endSkipTime)
}
func GetEmailContent(alertType string, params []dto.Param) string {
switch GetCronJobType(alertType) {
case "ssl":
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
case "siteEndTime":
return i18n.GetMsgWithMap("WebSiteAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
case "panelPwdEndTime":
return i18n.GetMsgWithMap("PanelPwdExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
case "licenseTime":
return i18n.GetMsgWithMap("LicenseExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
case "panelUpdate":
return i18n.GetMsgByKey("PanelVersionAlert")
case "cpu":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
case "memory":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
case "load":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
case "disk":
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2")})
case "cronJob":
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1")})
case "clams":
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
default:
return ""
}
}
func getValueByIndex(params []dto.Param, index string) string {
for _, p := range params {
if p.Index == index {
return p.Value
}
}
return ""
}