mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-21 00:24:12 +08:00
1100 lines
28 KiB
Go
1100 lines
28 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"sort"
|
|
"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"
|
|
"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/common"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/psutil"
|
|
versionUtil "github.com/1Panel-dev/1Panel/agent/utils/version"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
|
"github.com/shirou/gopsutil/v4/cpu"
|
|
"github.com/shirou/gopsutil/v4/disk"
|
|
"github.com/shirou/gopsutil/v4/load"
|
|
"github.com/shirou/gopsutil/v4/mem"
|
|
gnet "github.com/shirou/gopsutil/v4/net"
|
|
)
|
|
|
|
const (
|
|
ResourceAlertInterval = 30
|
|
CheckIntervalSec = 3
|
|
LoadCheckIntervalMin = 5
|
|
)
|
|
|
|
type AlertTaskHelper struct {
|
|
DiskIO chan []disk.IOCountersStat
|
|
NetIO chan []gnet.IOCountersStat
|
|
}
|
|
|
|
type IAlertTaskHelper interface {
|
|
StopTask()
|
|
StartTask()
|
|
ResetTask()
|
|
InitTask(alertType string)
|
|
}
|
|
|
|
var cpuLoad1, cpuLoad5, cpuLoad15 []float64
|
|
var memoryLoad1, memoryLoad5, memoryLoad15 []float64
|
|
var alertTaskMu sync.Mutex
|
|
|
|
var baseTypes = map[string]bool{"ssl": true, "siteEndTime": true, "panelPwdEndTime": true, "panelUpdate": true}
|
|
var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, "load": true, "panelLogin": true, "sshLogin": true, "nodeException": true, "licenseException": true}
|
|
|
|
func NewIAlertTaskHelper() IAlertTaskHelper {
|
|
return &AlertTaskHelper{
|
|
DiskIO: make(chan []disk.IOCountersStat, 1),
|
|
NetIO: make(chan []gnet.IOCountersStat, 1),
|
|
}
|
|
}
|
|
func (m *AlertTaskHelper) StartTask() {
|
|
alertTaskMu.Lock()
|
|
defer alertTaskMu.Unlock()
|
|
m.startTaskLocked()
|
|
}
|
|
|
|
func (m *AlertTaskHelper) startTaskLocked() {
|
|
baseAlert, resourceAlert := m.getClassifiedAlerts()
|
|
if len(baseAlert) == 0 && len(resourceAlert) == 0 {
|
|
return
|
|
}
|
|
handleBaseAlertsLocked(baseAlert)
|
|
handleResourceAlertsLocked(resourceAlert)
|
|
}
|
|
|
|
func (m *AlertTaskHelper) StopTask() {
|
|
alertTaskMu.Lock()
|
|
defer alertTaskMu.Unlock()
|
|
stopBaseJobLocked()
|
|
stopResourceJobLocked()
|
|
}
|
|
|
|
func (m *AlertTaskHelper) ResetTask() {
|
|
alertTaskMu.Lock()
|
|
defer alertTaskMu.Unlock()
|
|
stopBaseJobLocked()
|
|
stopResourceJobLocked()
|
|
m.startTaskLocked()
|
|
}
|
|
|
|
func (m *AlertTaskHelper) InitTask(alertType string) {
|
|
alertTaskMu.Lock()
|
|
defer alertTaskMu.Unlock()
|
|
m.initTaskLocked(alertType)
|
|
}
|
|
|
|
func (m *AlertTaskHelper) initTaskLocked(alertType string) {
|
|
resetAlertState(alertType)
|
|
if baseTypes[alertType] {
|
|
stopBaseJobLocked()
|
|
} else if resourceTypes[alertType] {
|
|
stopResourceJobLocked()
|
|
}
|
|
m.startTaskLocked()
|
|
}
|
|
|
|
func resetAlertState(alertType string) {
|
|
switch alertType {
|
|
case "cpu":
|
|
cpuLoad1 = []float64{}
|
|
cpuLoad5 = []float64{}
|
|
cpuLoad15 = []float64{}
|
|
case "memory":
|
|
memoryLoad1 = []float64{}
|
|
memoryLoad5 = []float64{}
|
|
memoryLoad15 = []float64{}
|
|
}
|
|
}
|
|
|
|
func (m *AlertTaskHelper) getClassifiedAlerts() (baseAlerts, resourceAlerts []dto.AlertDTO) {
|
|
alertList, _ := NewIAlertService().GetAlerts()
|
|
for _, alert := range alertList {
|
|
if baseTypes[alert.Type] {
|
|
baseAlerts = append(baseAlerts, alert)
|
|
} else if resourceTypes[alert.Type] {
|
|
resourceAlerts = append(resourceAlerts, alert)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func handleBaseAlertsLocked(baseAlerts []dto.AlertDTO) {
|
|
if len(baseAlerts) == 0 {
|
|
stopBaseJobLocked()
|
|
return
|
|
}
|
|
if global.AlertBaseJobID == 0 {
|
|
baseTask(baseAlerts)
|
|
jobID, err := global.Cron.AddFunc("@every 30m", func() {
|
|
baseTask(baseAlerts)
|
|
})
|
|
if err != nil {
|
|
global.LOG.Errorf("alert base job start failed: %v", err)
|
|
return
|
|
}
|
|
global.AlertBaseJobID = jobID
|
|
global.LOG.Info("start alert base job")
|
|
}
|
|
}
|
|
|
|
func handleResourceAlertsLocked(resourceAlerts []dto.AlertDTO) {
|
|
if len(resourceAlerts) == 0 {
|
|
stopResourceJobLocked()
|
|
return
|
|
}
|
|
if global.AlertResourceJobID == 0 {
|
|
jobID, err := global.Cron.AddFunc("*/1 * * * *", func() {
|
|
resourceTask(resourceAlerts)
|
|
})
|
|
if err != nil {
|
|
global.LOG.Errorf("alert resource job start failed: %v", err)
|
|
return
|
|
}
|
|
global.AlertResourceJobID = jobID
|
|
global.LOG.Info("start alert resource job")
|
|
}
|
|
}
|
|
|
|
func stopBaseJobLocked() {
|
|
if global.AlertBaseJobID != 0 {
|
|
global.Cron.Remove(global.AlertBaseJobID)
|
|
global.AlertBaseJobID = 0
|
|
global.LOG.Info("stop alert base job")
|
|
}
|
|
}
|
|
|
|
func stopResourceJobLocked() {
|
|
if global.AlertResourceJobID != 0 {
|
|
global.Cron.Remove(global.AlertResourceJobID)
|
|
global.AlertResourceJobID = 0
|
|
global.LOG.Info("stop alert resource job")
|
|
}
|
|
}
|
|
|
|
func baseTask(baseAlert []dto.AlertDTO) {
|
|
for _, alert := range baseAlert {
|
|
if !alertUtil.CheckSendTimeRange(alert.Type) {
|
|
continue
|
|
}
|
|
switch alert.Type {
|
|
case "ssl":
|
|
loadSSLInfo(alert)
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func resourceTask(resourceAlert []dto.AlertDTO) {
|
|
minute := time.Now().Minute()
|
|
for _, alert := range resourceAlert {
|
|
if !alertUtil.CheckSendTimeRange(alert.Type) {
|
|
continue
|
|
}
|
|
execute := minute%LoadCheckIntervalMin == 0
|
|
switch alert.Type {
|
|
case "cpu":
|
|
loadCPUUsage(alert)
|
|
case "memory":
|
|
loadMemUsage(alert)
|
|
case "load":
|
|
loadLoadInfo(alert)
|
|
case "disk":
|
|
loadDiskUsage(alert)
|
|
case "panelLogin":
|
|
loadPanelLogin(alert)
|
|
case "sshLogin":
|
|
loadSSHLogin(alert)
|
|
case "nodeException":
|
|
if execute && global.IsMaster {
|
|
loadNodeException(alert)
|
|
}
|
|
case "licenseException":
|
|
if global.CONF.Base.IsEnterprise {
|
|
continue
|
|
}
|
|
if execute && global.IsMaster {
|
|
loadLicenseException(alert)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadSSLInfo(alert dto.AlertDTO) {
|
|
opts := getRepoOptionsByProject(alert.Project)
|
|
sslList, _ := repo.NewISSLRepo().List(opts...)
|
|
if len(sslList) == 0 {
|
|
return
|
|
}
|
|
daysDiffMap, projectMap := calculateSSLExpiryDays(sslList, alert.Cycle)
|
|
projectJSON := serializeAndSortProjects(projectMap)
|
|
if projectJSON == "" || len(daysDiffMap) == 0 {
|
|
return
|
|
}
|
|
sender := NewAlertSender(alert, projectJSON)
|
|
minDays := math.MaxInt
|
|
maxDays := 0
|
|
allDomains := make([]string, 0)
|
|
for daysDiff, domains := range daysDiffMap {
|
|
allDomains = append(allDomains, domains...)
|
|
if daysDiff < minDays {
|
|
minDays = daysDiff
|
|
}
|
|
if daysDiff > maxDays {
|
|
maxDays = daysDiff
|
|
}
|
|
}
|
|
if len(allDomains) == 0 {
|
|
return
|
|
}
|
|
var daysStr string
|
|
if len(allDomains) == 1 {
|
|
daysStr = strconv.Itoa(minDays)
|
|
} else {
|
|
daysStr = strconv.Itoa(minDays) + "-" + strconv.Itoa(maxDays)
|
|
}
|
|
domainStr := strings.Join(allDomains, ",")
|
|
params := createAlertBaseParams(strconv.Itoa(len(daysDiffMap)), daysStr)
|
|
sender.Send(domainStr, params)
|
|
}
|
|
|
|
func loadWebsiteInfo(alert dto.AlertDTO) {
|
|
opts := getRepoOptionsByProject(alert.Project)
|
|
websiteList, _ := websiteRepo.List(opts...)
|
|
if len(websiteList) == 0 {
|
|
return
|
|
}
|
|
|
|
daysDiffMap, projectMap := calculateWebsiteExpiryDays(websiteList, alert.Cycle)
|
|
projectJSON := serializeAndSortProjects(projectMap)
|
|
if projectJSON == "" || len(daysDiffMap) == 0 {
|
|
return
|
|
}
|
|
sender := NewAlertSender(alert, projectJSON)
|
|
minDays := math.MaxInt
|
|
maxDays := 0
|
|
allDomains := make([]string, 0)
|
|
for daysDiff, domains := range daysDiffMap {
|
|
allDomains = append(allDomains, domains...)
|
|
if daysDiff < minDays {
|
|
minDays = daysDiff
|
|
}
|
|
if daysDiff > maxDays {
|
|
maxDays = daysDiff
|
|
}
|
|
}
|
|
if len(allDomains) == 0 {
|
|
return
|
|
}
|
|
var daysStr string
|
|
if len(allDomains) == 1 {
|
|
daysStr = strconv.Itoa(minDays)
|
|
} else {
|
|
daysStr = strconv.Itoa(minDays) + "-" + strconv.Itoa(maxDays)
|
|
}
|
|
domainStr := strings.Join(allDomains, ",")
|
|
params := createAlertBaseParams(strconv.Itoa(len(daysDiffMap)), daysStr)
|
|
sender.Send(domainStr, params)
|
|
}
|
|
|
|
func loadPanelPwd(alert dto.AlertDTO) {
|
|
// only master alert
|
|
expDays, err := getSettingValue("ExpirationDays")
|
|
if err != nil || expDays == "0" {
|
|
global.LOG.Info("panel password expiration setting not enabled, skip")
|
|
return
|
|
}
|
|
|
|
expTimeStr, err := getSettingValue("ExpirationTime")
|
|
if err != nil {
|
|
return
|
|
}
|
|
expTime, _ := time.Parse(constant.DateTimeLayout, expTimeStr)
|
|
daysDiff := calculateDaysDifference(expTime)
|
|
if daysDiff >= 0 && int(alert.Cycle) >= daysDiff {
|
|
params := createAlertPwdParams(strconv.Itoa(daysDiff))
|
|
sender := NewAlertSender(alert, expTimeStr)
|
|
sender.Send(strconv.Itoa(daysDiff), params)
|
|
}
|
|
}
|
|
|
|
func loadPanelUpdate(alert dto.AlertDTO) {
|
|
// only master alert
|
|
info, err := versionUtil.GetUpgradeVersionInfo()
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting version info: %s", err)
|
|
return
|
|
}
|
|
|
|
version := getValidVersion(info)
|
|
if version == "" {
|
|
return
|
|
}
|
|
|
|
sender := NewAlertSender(alert, version)
|
|
sender.Send(version, []dto.Param{})
|
|
}
|
|
|
|
// 获取 CPU 使用率数据并发送到通道
|
|
func loadCPUUsage(alert dto.AlertDTO) {
|
|
percent, err := cpu.Percent(time.Duration(CheckIntervalSec)*time.Second, false)
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting cpu usage, err: %v", err)
|
|
return
|
|
}
|
|
|
|
if len(percent) > 0 {
|
|
var usageLoad *[]float64
|
|
var threshold int
|
|
|
|
switch alert.Cycle {
|
|
case 1:
|
|
usageLoad = &cpuLoad1
|
|
threshold = 1
|
|
case 5:
|
|
usageLoad = &cpuLoad5
|
|
threshold = 5
|
|
case 15:
|
|
usageLoad = &cpuLoad15
|
|
threshold = 15
|
|
}
|
|
shouldSendResourceAlert(alert, percent[0], usageLoad, threshold)
|
|
}
|
|
}
|
|
|
|
// 获取内存使用情况数据并发送到通道
|
|
func loadMemUsage(alert dto.AlertDTO) {
|
|
memStat, err := mem.VirtualMemory()
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting memory usage, err: %v", err)
|
|
return
|
|
}
|
|
|
|
percent := memStat.UsedPercent
|
|
var memoryLoad *[]float64
|
|
var threshold int
|
|
|
|
switch alert.Cycle {
|
|
case 1:
|
|
memoryLoad = &memoryLoad1
|
|
threshold = 1
|
|
case 5:
|
|
memoryLoad = &memoryLoad5
|
|
threshold = 5
|
|
case 15:
|
|
memoryLoad = &memoryLoad15
|
|
threshold = 15
|
|
}
|
|
shouldSendResourceAlert(alert, percent, memoryLoad, threshold)
|
|
}
|
|
|
|
// 获取系统负载数据并发送到通道
|
|
func loadLoadInfo(alert dto.AlertDTO) {
|
|
avgStat, err := load.Avg()
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting load usage, err: %v", err)
|
|
return
|
|
}
|
|
var loadValue float64
|
|
CPUTotal, _ := psutil.CPUInfo.GetLogicalCores(false)
|
|
switch alert.Cycle {
|
|
case 1:
|
|
loadValue = avgStat.Load1 / (float64(CPUTotal*2) * 0.75) * 100
|
|
case 5:
|
|
loadValue = avgStat.Load5 / (float64(CPUTotal*2) * 0.75) * 100
|
|
case 15:
|
|
loadValue = avgStat.Load15 / (float64(CPUTotal*2) * 0.75) * 100
|
|
default:
|
|
return
|
|
}
|
|
if loadValue < float64(alert.Count) {
|
|
return
|
|
}
|
|
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
|
if err != nil {
|
|
global.LOG.Errorf("task log record not found, err: %v", err)
|
|
}
|
|
if isAlertDue(newDate) {
|
|
sendResourceAlert(alert, loadValue)
|
|
}
|
|
}
|
|
|
|
func loadDiskUsage(alert dto.AlertDTO) {
|
|
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
|
if err != nil {
|
|
global.LOG.Errorf("record not found, err: %v", err)
|
|
return
|
|
}
|
|
if isAlertDue(newDate) {
|
|
if strings.Contains(alert.Project, "all") {
|
|
_ = processAllDisks(alert)
|
|
} else {
|
|
_ = processSingleDisk(alert)
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadPanelLogin(alert dto.AlertDTO) {
|
|
count, isAlert, err := alertUtil.CountRecentFailedLoginLogs(alert.Cycle, alert.Count)
|
|
if err != nil {
|
|
global.LOG.Errorf("Failed to count recent failed login logs: %v", err)
|
|
}
|
|
if isAlert {
|
|
params := []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: "",
|
|
},
|
|
{
|
|
Index: "2",
|
|
Key: "project",
|
|
Value: "",
|
|
},
|
|
}
|
|
sendAlerts(alert, "panelLogin", strconv.Itoa(count), "panelLogin", params)
|
|
}
|
|
|
|
whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n")
|
|
records, err := alertUtil.FindRecentSuccessLoginsNotInWhitelist(30, whitelist)
|
|
if err != nil {
|
|
global.LOG.Errorf("Failed to check recent failed ip login logs: %v", err)
|
|
}
|
|
records = filterLoginLogsNotInWhitelist(records, whitelist)
|
|
if len(records) > 0 {
|
|
quota := strings.Join(func() []string {
|
|
var ips []string
|
|
for _, r := range records {
|
|
ips = append(ips, r.IP)
|
|
}
|
|
return ips
|
|
}(), "\n")
|
|
params := []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: "",
|
|
},
|
|
{
|
|
Index: "2",
|
|
Key: "project",
|
|
Value: " IP ",
|
|
},
|
|
}
|
|
sendAlerts(alert, "panelIpLogin", quota, "panelIpLogin", params)
|
|
}
|
|
}
|
|
|
|
func loadSSHLogin(alert dto.AlertDTO) {
|
|
count, isAlert, err := alertUtil.CountRecentFailedSSHLog(alert.Cycle, alert.Count)
|
|
if err != nil {
|
|
global.LOG.Errorf("Failed to count recent failed ssh login logs: %v", err)
|
|
}
|
|
if isAlert {
|
|
params := []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: " SSH ",
|
|
},
|
|
{
|
|
Index: "2",
|
|
Key: "project",
|
|
Value: "",
|
|
},
|
|
}
|
|
sendAlerts(alert, "sshLogin", strconv.Itoa(count), "sshLogin", params)
|
|
}
|
|
whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n")
|
|
records, err := alertUtil.FindRecentSuccessLoginNotInWhitelist(30, whitelist)
|
|
if err != nil {
|
|
global.LOG.Errorf("Failed to check recent failed ip ssh login logs: %v", err)
|
|
}
|
|
records = filterSSHLoginEntriesNotInWhitelist(records, whitelist)
|
|
if len(records) > 0 {
|
|
quota := strings.Join(records, "\n")
|
|
params := []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: " SSH ",
|
|
},
|
|
{
|
|
Index: "2",
|
|
Key: "project",
|
|
Value: " IP ",
|
|
},
|
|
}
|
|
sendAlerts(alert, "sshIpLogin", quota, "sshIpLogin", params)
|
|
}
|
|
}
|
|
|
|
func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog {
|
|
filtered := make([]model.LoginLog, 0, len(records))
|
|
for _, record := range records {
|
|
if !isIPInWhitelist(record.IP, whitelist) {
|
|
filtered = append(filtered, record)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func filterSSHLoginEntriesNotInWhitelist(records []string, whitelist []string) []string {
|
|
filtered := make([]string, 0, len(records))
|
|
for _, record := range records {
|
|
ip := record
|
|
if idx := strings.Index(record, "-"); idx >= 0 {
|
|
ip = record[:idx]
|
|
}
|
|
if !isIPInWhitelist(ip, whitelist) {
|
|
filtered = append(filtered, record)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func isIPInWhitelist(ip string, whitelist []string) bool {
|
|
targetIP := net.ParseIP(strings.TrimSpace(ip))
|
|
if targetIP == nil {
|
|
return false
|
|
}
|
|
for _, item := range whitelist {
|
|
item = strings.TrimSpace(item)
|
|
if item == "" {
|
|
continue
|
|
}
|
|
if item == ip {
|
|
return true
|
|
}
|
|
if whiteIP := net.ParseIP(item); whiteIP != nil {
|
|
if whiteIP.Equal(targetIP) {
|
|
return true
|
|
}
|
|
continue
|
|
}
|
|
_, ipNet, err := net.ParseCIDR(item)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if ipNet.Contains(targetIP) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func loadNodeException(alert dto.AlertDTO) {
|
|
// only master alert
|
|
failCount, err := xpack.AlertProvider.GetNodeErrorAlert()
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting node, err: %s", err)
|
|
return
|
|
}
|
|
if failCount > 0 {
|
|
quotaType := "node-error"
|
|
params := []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: strconv.Itoa(int(failCount)),
|
|
},
|
|
}
|
|
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
|
if err != nil {
|
|
global.LOG.Errorf("record not found, err: %v", err)
|
|
return
|
|
}
|
|
if isAlertDue(newDate) {
|
|
sender := NewAlertSender(alert, quotaType)
|
|
sender.ResourceSend(strconv.Itoa(int(failCount)), params)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func loadLicenseException(alert dto.AlertDTO) {
|
|
// only master alert
|
|
failCount, err := xpack.AlertProvider.GetLicenseErrorAlert()
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting license, err: %s", err)
|
|
return
|
|
}
|
|
if failCount > 0 {
|
|
quotaType := "license-error"
|
|
params := []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: strconv.Itoa(int(failCount)),
|
|
},
|
|
}
|
|
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
|
if err != nil {
|
|
global.LOG.Errorf("record not found, err: %v", err)
|
|
return
|
|
}
|
|
if isAlertDue(newDate) {
|
|
sender := NewAlertSender(alert, quotaType)
|
|
sender.ResourceSend(strconv.Itoa(int(failCount)), params)
|
|
}
|
|
}
|
|
}
|
|
|
|
func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param) {
|
|
methods := strings.Split(alert.Method, ",")
|
|
newDate, err := alertRepo.GetTaskLog(alertType, alert.ID)
|
|
if err != nil {
|
|
global.LOG.Errorf("task log record not found, err: %v", err)
|
|
}
|
|
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
|
|
for _, m := range methods {
|
|
m = strings.TrimSpace(m)
|
|
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 {
|
|
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
|
|
if project != "all" {
|
|
itemID, _ := strconv.Atoi(project)
|
|
opts = append(opts, repo.WithByID(uint(itemID)))
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func serializeAndSortProjects(projectMap map[uint][]time.Time) string {
|
|
if len(projectMap) == 0 {
|
|
return ""
|
|
}
|
|
keys := make([]int, 0, len(projectMap))
|
|
for k := range projectMap {
|
|
keys = append(keys, int(k))
|
|
}
|
|
sort.Ints(keys)
|
|
projectJSON, err := json.Marshal(projectMap)
|
|
if err != nil {
|
|
global.LOG.Errorf("Failed to serialize projectMap: %v", err)
|
|
return ""
|
|
}
|
|
|
|
return string(projectJSON)
|
|
}
|
|
|
|
func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
|
currentDate := time.Now()
|
|
daysDiffMap := make(map[int][]string)
|
|
projectMap := make(map[uint][]time.Time)
|
|
|
|
for _, ssl := range sslList {
|
|
daysDiff := int(math.Ceil(
|
|
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
|
|
))
|
|
if daysDiff > 0 && int(cycle) >= daysDiff {
|
|
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
|
|
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
|
|
}
|
|
}
|
|
return daysDiffMap, projectMap
|
|
}
|
|
|
|
func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) {
|
|
currentDate := time.Now()
|
|
daysDiffMap := make(map[int][]string)
|
|
projectMap := make(map[uint][]time.Time)
|
|
|
|
for _, website := range websites {
|
|
daysDiff := int(math.Ceil(
|
|
website.ExpireDate.Sub(currentDate).Hours() / 24,
|
|
))
|
|
if daysDiff > 0 && int(cycle) >= daysDiff {
|
|
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], website.PrimaryDomain)
|
|
projectMap[website.ID] = append(projectMap[website.ID], website.ExpireDate)
|
|
}
|
|
}
|
|
return daysDiffMap, projectMap
|
|
}
|
|
|
|
func getSettingValue(key string) (string, error) {
|
|
var setting model.Setting
|
|
if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", key).First(&setting).Error; err != nil {
|
|
global.LOG.Errorf("load %s from db setting failed: %v", key, err)
|
|
return "", err
|
|
}
|
|
return setting.Value, nil
|
|
}
|
|
|
|
func getValidVersion(info *dto.UpgradeInfo) string {
|
|
if info.NewVersion != "" {
|
|
return info.NewVersion
|
|
} else if info.TestVersion != "" {
|
|
return info.TestVersion
|
|
} else if info.LatestVersion != "" {
|
|
return info.LatestVersion
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func shouldSendResourceAlert(alert dto.AlertDTO, currentUsage float64, usageLoad *[]float64, threshold int) {
|
|
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
|
if err != nil {
|
|
global.LOG.Errorf("record not found, err: %v", err)
|
|
}
|
|
if isAlertDue(newDate) {
|
|
*usageLoad = append(*usageLoad, currentUsage)
|
|
if len(*usageLoad) > threshold {
|
|
*usageLoad = (*usageLoad)[1:]
|
|
}
|
|
if len(*usageLoad) == threshold {
|
|
avgUsage := average(*usageLoad)
|
|
if avgUsage >= float64(alert.Count) {
|
|
sendResourceAlert(alert, avgUsage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func isAlertDue(lastAlertTime time.Time) bool {
|
|
if lastAlertTime.IsZero() {
|
|
return true
|
|
}
|
|
return calculateMinutesDifference(lastAlertTime) > ResourceAlertInterval
|
|
}
|
|
|
|
func sendResourceAlert(alert dto.AlertDTO, value float64) {
|
|
valueStr := common.FormatPercent(value)
|
|
module := getModuleName(alert.Type)
|
|
params := createAlertAvgParams(strconv.Itoa(int(alert.Cycle)), module, valueStr)
|
|
sender := NewAlertSender(alert, strconv.Itoa(int(alert.Cycle)))
|
|
sender.ResourceSend(valueStr, params)
|
|
}
|
|
|
|
func getModuleName(alertType string) string {
|
|
var module string
|
|
switch alertType {
|
|
case "cpu":
|
|
module = " CPU "
|
|
case "memory":
|
|
module = "内存"
|
|
case "load":
|
|
module = "负载"
|
|
default:
|
|
}
|
|
return module
|
|
}
|
|
|
|
func canSendAlertToday(alertType, quotaType string, sendCount uint, method string) (uint, bool) {
|
|
todayCount, _, err := alertRepo.LoadTaskCount(alertType, quotaType, method)
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting task info, err: %v", err)
|
|
return todayCount, false
|
|
}
|
|
if todayCount >= sendCount {
|
|
return todayCount, false
|
|
}
|
|
|
|
return todayCount, true
|
|
}
|
|
|
|
func average(arr []float64) float64 {
|
|
total := 0.0
|
|
for _, v := range arr {
|
|
total += v
|
|
}
|
|
return total / float64(len(arr))
|
|
}
|
|
|
|
func createAlertBaseParams(project, cycle string) []dto.Param {
|
|
return []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "project",
|
|
Value: project,
|
|
},
|
|
{
|
|
Index: "2",
|
|
Key: "cycle",
|
|
Value: cycle,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createAlertPwdParams(cycle string) []dto.Param {
|
|
return []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: cycle,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createAlertAvgParams(cycle, module, count string) []dto.Param {
|
|
return []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "cycle",
|
|
Value: cycle,
|
|
},
|
|
{
|
|
Index: "2",
|
|
Key: "module",
|
|
Value: module,
|
|
},
|
|
{
|
|
Index: "3",
|
|
Key: "count",
|
|
Value: count,
|
|
},
|
|
}
|
|
}
|
|
|
|
func createAlertDiskParams(project, count string) []dto.Param {
|
|
return []dto.Param{
|
|
{
|
|
Index: "1",
|
|
Key: "project",
|
|
Value: project,
|
|
},
|
|
{
|
|
Index: "2",
|
|
Key: "count",
|
|
Value: count,
|
|
},
|
|
}
|
|
}
|
|
|
|
func processAllDisks(alert dto.AlertDTO) error {
|
|
diskList, err := NewIAlertService().GetDisks()
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting disk list, err: %v", err)
|
|
return err
|
|
}
|
|
var errMsgs []string
|
|
for _, item := range diskList {
|
|
err := checkAndCreateDiskAlert(alert, item.Path)
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("disk path %s process failed: %v", item.Path, err)
|
|
errMsgs = append(errMsgs, errMsg)
|
|
global.LOG.Errorf("%s", errMsg)
|
|
continue
|
|
}
|
|
}
|
|
if len(errMsgs) > 0 {
|
|
return fmt.Errorf("batch process disks failed, error count: %d, details: %s", len(errMsgs), strings.Join(errMsgs, "; "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func processSingleDisk(alert dto.AlertDTO) error {
|
|
err := checkAndCreateDiskAlert(alert, alert.Project)
|
|
if err != nil {
|
|
global.LOG.Errorf("%s", err.Error())
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) error {
|
|
usageStat, err := psutil.DISK.GetUsage(path, false)
|
|
if err != nil {
|
|
global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
|
|
return err
|
|
}
|
|
|
|
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
|
|
commonTotal := float64(alert.Count)
|
|
if alert.Cycle == 1 {
|
|
commonTotal *= 1024 * 1024 * 1024
|
|
}
|
|
if usedTotal < commonTotal {
|
|
return nil
|
|
}
|
|
params := createAlertDiskParams(path, usedStr)
|
|
sender := NewAlertSender(alert, alert.Project)
|
|
sender.ResourceSend(path, params)
|
|
return nil
|
|
}
|
|
|
|
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
|
|
if cycle == 1 {
|
|
return float64(usageStat.Used), common.FormatBytes(usageStat.Used)
|
|
}
|
|
return usageStat.UsedPercent, common.FormatPercent(usageStat.UsedPercent)
|
|
}
|
|
|
|
func calculateDaysDifference(expirationTime time.Time) int {
|
|
currentDate := time.Now()
|
|
formattedTime := currentDate.Format(constant.DateTimeLayout)
|
|
parsedTime, _ := time.Parse(constant.DateTimeLayout, formattedTime)
|
|
timeGap := expirationTime.Sub(parsedTime).Milliseconds()
|
|
if timeGap < 0 {
|
|
return -1
|
|
}
|
|
daysDifference := int(math.Floor(float64(timeGap) / (3600 * 1000 * 24)))
|
|
return daysDifference
|
|
}
|
|
|
|
func calculateMinutesDifference(newDate time.Time) int {
|
|
now := time.Now()
|
|
if newDate.After(now) {
|
|
return -1
|
|
}
|
|
minutesDifference := int(now.Sub(newDate).Minutes())
|
|
return minutesDifference
|
|
}
|