feat: Support more alert method (#11742)

This commit is contained in:
2026-01-30 08:38:50 +00:00
committed by GitHub
parent 91745f592f
commit 5aa3c29684
25 changed files with 714 additions and 94 deletions
+5
View File
@@ -335,3 +335,8 @@ type AgentInfo struct {
NodeName string `json:"nodeName"`
NodeAddr string `json:"nodeAddr"`
}
type AlertWebhookConfig struct {
DisplayName string `json:"displayName"`
Url string `json:"url"`
}
+36 -14
View File
@@ -2,6 +2,7 @@ package service
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -586,8 +587,6 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
continue
}
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", alertType)
case constant.Email:
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.Email)
if !isValid {
@@ -610,7 +609,25 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
continue
}
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", alertType)
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.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err := xpack.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, alertUtil.GetCronJobType(alert.Type), quotaType, m)
default:
}
}
}
@@ -829,30 +846,36 @@ func processAllDisks(alert dto.AlertDTO) error {
global.LOG.Errorf("error getting disk list, err: %v", err)
return err
}
var errMsgs []string
for _, item := range diskList {
if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success {
global.LOG.Infof("disk alert pushed successfully for %s", item.Path)
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(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 {
success, err := checkAndCreateDiskAlert(alert, alert.Project)
err := checkAndCreateDiskAlert(alert, alert.Project)
if err != nil {
global.LOG.Errorf(err.Error())
return err
}
if success {
global.LOG.Infof("disk alert pushed successfully for %s", alert.Project)
}
return nil
}
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
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 false, err
return err
}
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
@@ -861,13 +884,12 @@ func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
commonTotal *= 1024 * 1024 * 1024
}
if usedTotal < commonTotal {
return false, nil
return nil
}
global.LOG.Infof("disk「 %s 」usage: %s", path, usedStr)
params := createAlertDiskParams(path, usedStr)
sender := NewAlertSender(alert, alert.Project)
sender.ResourceSend(path, params)
return true, nil
return nil
}
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
+57 -6
View File
@@ -30,6 +30,8 @@ func (s *AlertSender) Send(quota string, params []dto.Param) {
s.sendSMS(quota, params)
case constant.Email:
s.sendEmail(quota, params)
case constant.WeCom, constant.DingTalk, constant.FeiShu:
s.sendWebhook(quota, params, method)
}
}
}
@@ -43,6 +45,8 @@ func (s *AlertSender) ResourceSend(quota string, params []dto.Param) {
s.sendResourceSMS(quota, params)
case constant.Email:
s.sendResourceEmail(quota, params)
case constant.WeCom, constant.DingTalk, constant.FeiShu:
s.sendResourceWebhook(quota, params, method)
}
}
}
@@ -64,9 +68,12 @@ func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
Type: s.alert.Type,
}
_ = xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
if err != nil {
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
}
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
@@ -86,9 +93,34 @@ func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
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)
global.LOG.Infof("%s alert email push successful", s.alert.Type)
}
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
totalCount, isValid := s.canSendAlert(method)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
}
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
@@ -113,7 +145,6 @@ func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
}
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
@@ -138,7 +169,27 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", s.alert.Type)
}
func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, method string) {
todayCount, isValid := s.canResourceSendAlert(method)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
if err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo); err != nil {
global.LOG.Errorf("failed to send webhook alert: %v", err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
}
func (s *AlertSender) canSendAlert(method string) (uint, bool) {
+1
View File
@@ -24,4 +24,5 @@ const (
WeCom = "weCom"
DingTalk = "dingTalk"
FeiShu = "feiShu"
Custom = "custom"
)
+17 -11
View File
@@ -49,19 +49,24 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
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 {
return err
}
if emailInfo.Host == "" {
create.Message = "email config is required"
create.Status = constant.AlertError
return SaveAlertLog(create, &alertLog)
}
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
}
username := emailInfo.UserName
if username == "" {
username = emailInfo.Sender
@@ -76,7 +81,7 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
Encryption: emailInfo.Encryption,
Recipient: emailInfo.Recipient,
}
content := GetEmailContent(alert.Type, params, agentInfo)
content := GetSendContent(alert.Type, params, agentInfo)
if content == "" {
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
}
@@ -122,6 +127,7 @@ func CreateNewAlertTask(quota, alertType, quotaType, method string) {
if err != nil {
global.LOG.Errorf("error creating alert tasks, err: %v", err)
}
global.LOG.Infof("%s alert %s push completed", alertType, method)
}
func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param, method string) string {
@@ -300,7 +306,7 @@ func isWithinTimeRange(savedTimeString string) bool {
return now.After(skipTime) && now.Before(endSkipTime)
}
func GetEmailContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
switch GetCronJobType(alertType) {
case "ssl":
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
+25 -4
View File
@@ -42,9 +42,12 @@ func PushAlert(pushAlert dto.PushAlert) error {
AlertId: alert.ID,
Count: todayCount + 1,
}
_ = xpack.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
err = xpack.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)
global.LOG.Infof("%s %s alert push successful", alert.Type, 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 {
@@ -59,10 +62,28 @@ func PushAlert(pushAlert dto.PushAlert) error {
agentInfo, _ := xpack.GetAgentInfo()
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport, agentInfo)
if err != nil {
return err
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)
global.LOG.Infof("%s %s alert push successful", alert.Type, constant.Email)
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.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err = xpack.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:
}
}
+8
View File
@@ -61,6 +61,14 @@ func CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogC
return nil
}
func CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
return nil
}
func 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 GetLicenseErrorAlert() (uint, error) {
return 0, nil
}
+1
View File
@@ -7,4 +7,5 @@ const (
WeCom = "weCom"
DingTalk = "dingTalk"
FeiShu = "feiShu"
Custom = "custom"
)
+48
View File
@@ -159,9 +159,57 @@ export namespace Alert {
recipient: string;
}
export interface CommonAlertConfig {
id?: number;
type: string;
title: string;
status: string;
config: CommonConfig;
}
export interface CommonConfig {
isOffline?: string;
alertDailyNum?: number;
alertSendTimeRange?: string;
}
export interface EmailConfig {
id?: number;
type: string;
title: string;
status: string;
config: {
status?: string;
sender?: string;
userName?: string;
password?: string;
displayName?: string;
host?: string;
port?: number;
encryption?: string;
recipient?: string;
};
}
export interface SmsConfig {
id?: number;
type: string;
title: string;
status: string;
config: {
phone?: string;
alertDailyNum?: number;
};
}
export interface WebhookConfig {
id?: number;
type: string;
title: string;
status: string;
config: {
displayName?: string;
url?: string;
};
}
}
+8 -1
View File
@@ -3939,7 +3939,7 @@ const message = {
alertCount: 'Alert Count',
clamHelper: 'Trigger alert when scanning infected files',
cronJobHelper: 'Trigger alert when task execution fails',
licenseHelper: 'Professional version supports SMS alert',
licenseHelper: 'Professional version supports more alert',
alertCountHelper: 'Maximum daily alarm frequency',
alert: 'SMS Alert',
logs: 'Alert Logs',
@@ -4141,6 +4141,13 @@ const message = {
panelLoginRule: 'Panel login alert, sent {0} times per day',
sshLoginRule: 'SSH login alert, sent {0} times per day',
userNameHelper: 'Username is empty, the sender address will be used by default',
alertConfigHelper: 'Configure alert notification channels to receive panel message push',
weComConfigHelper: 'WeCom alert notification configuration',
wechatConfigHelper: 'WeChat Official Account alert notification configuration',
dingTalkConfigHelper: 'DingTalk alert notification configuration',
feiShuConfigHelper: 'Feishu alert notification configuration',
webhookName: 'Bot name or remark',
webhookUrl: 'Webhook URL',
},
theme: {
lingXiaGold: 'Ling Xia Gold',
+8 -1
View File
@@ -3905,7 +3905,7 @@ const message = {
alertCount: 'Número de alertas',
clamHelper: 'Generar alerta al detectar archivos infectados en escaneo',
cronJobHelper: 'Generar alerta cuando falle la ejecución de una tarea programada',
licenseHelper: 'La versión Pro soporta alertas SMS',
licenseHelper: 'La versión Pro soporta alertas',
alertCountHelper: 'Frecuencia máxima diaria de alertas',
alert: 'Alerta SMS',
logs: 'Logs de alertas',
@@ -4098,6 +4098,13 @@ const message = {
panelLoginRule: 'Alerta de login en panel, {0} envíos/día',
sshLoginRule: 'Alerta de login SSH, {0} envíos/día',
userNameHelper: 'El nombre de usuario está vacío, se usará la dirección del remitente por defecto',
alertConfigHelper: 'Configurar canales de notificación de alerta para recibir mensajes push del panel',
weComConfigHelper: 'Configuración de notificación de alerta WeCom',
wechatConfigHelper: 'Configuración de notificación de alerta de Cuenta Oficial WeChat',
dingTalkConfigHelper: 'Configuración de notificación de alerta DingTalk',
feiShuConfigHelper: 'Configuración de notificación de alerta Feishu',
webhookName: 'Nombre del bot o nota',
webhookUrl: 'URL de Webhook',
},
theme: {
lingXiaGold: 'Ling Xia Gold',
+8 -1
View File
@@ -3828,7 +3828,7 @@ const message = {
alertCount: 'アラート数',
clamHelper: '感染したファイルをスキャンするときにアラートをトリガーします',
cronJobHelper: 'タスクの実行が失敗したときにアラートをトリガーします',
licenseHelper: 'プロのバージョンはSMSアラートをサポートします',
licenseHelper: 'プロのバージョンはアラートをサポートします',
alertCountHelper: '最大毎日のアラーム周波数',
alert: 'SMSアラート',
logs: 'アラートログ',
@@ -4026,6 +4026,13 @@ const message = {
panelLoginRule: 'パネルログインアラートは1日あたり{0}回送信',
sshLoginRule: 'SSHログインアラートは1日あたり{0}回送信',
userNameHelper: 'ユーザー名が空の場合送信者のアドレスがデフォルトで使用されます',
alertConfigHelper: 'パネルメッセージプッシュを受信するためのアラート通知チャネルを設定',
weComConfigHelper: 'WeComアラート通知設定',
wechatConfigHelper: 'WeChat公式アカウントアラート通知設定',
dingTalkConfigHelper: 'DingTalkアラート通知設定',
feiShuConfigHelper: 'Feishuアラート通知設定',
webhookName: 'ボット名または',
webhookUrl: 'Webhook URL',
},
theme: {
lingXiaGold: '凌霞金',
+8 -1
View File
@@ -3756,7 +3756,7 @@ const message = {
alertCount: '알림 횟수',
clamHelper: '감염된 파일을 스캔할 알림 트리거',
cronJobHelper: '작업 실행 실패 알림 트리거',
licenseHelper: '전문 버전에서는 SMS 알림을 지원합니다.',
licenseHelper: '전문 버전에서는 알림을 지원합니다.',
alertCountHelper: '최대 일일 알림 빈도',
alert: 'SMS 알림',
logs: '알림 로그',
@@ -3949,6 +3949,13 @@ const message = {
panelLoginRule: '패널 로그인 알림은 하루 {0} 전송',
sshLoginRule: 'SSH 로그인 알림은 하루 {0} 전송',
userNameHelper: '사용자 이름이 비어 있으면 기본적으로 발신자 주소가 사용됩니다',
alertConfigHelper: '패널 메시지 푸시를 수신하기 위한 알림 채널 구성',
weComConfigHelper: 'WeCom 알림 구성',
wechatConfigHelper: 'WeChat 공식 계정 알림 구성',
dingTalkConfigHelper: 'DingTalk 알림 구성',
feiShuConfigHelper: 'Feishu 알림 구성',
webhookName: ' 이름 또는 비고',
webhookUrl: 'Webhook URL',
},
theme: {
lingXiaGold: '링샤 골드',
+8 -1
View File
@@ -3898,7 +3898,7 @@ const message = {
alertCount: 'Bilangan Amaran',
clamHelper: 'Hantar amaran apabila terdapat fail yang dijangkiti semasa imbasan',
cronJobHelper: 'Hantar amaran apabila pelaksanaan tugas gagal',
licenseHelper: 'Versi profesional menyokong amaran SMS',
licenseHelper: 'Versi profesional menyokong amaran',
alertCountHelper: 'Kekerapan maksimum amaran harian',
alert: 'Amaran SMS',
logs: 'Log Amaran',
@@ -4104,6 +4104,13 @@ const message = {
panelLoginRule: 'Amaran log masuk panel, dihantar {0} kali sehari',
sshLoginRule: 'Amaran log masuk SSH, dihantar {0} kali sehari',
userNameHelper: 'Nama pengguna kosong, alamat penghantar akan digunakan secara lalai',
alertConfigHelper: 'Konfigurasikan saluran pemberitahuan amaran untuk menerima tolakan mesej panel',
weComConfigHelper: 'Konfigurasi pemberitahuan amaran WeCom',
wechatConfigHelper: 'Konfigurasi pemberitahuan amaran Akaun Rasmi WeChat',
dingTalkConfigHelper: 'Konfigurasi pemberitahuan amaran DingTalk',
feiShuConfigHelper: 'Konfigurasi pemberitahuan amaran Feishu',
webhookName: 'Nama bot atau catatan',
webhookUrl: 'URL Webhook',
},
theme: {
lingXiaGold: 'Ling Xia Emas',
+8 -1
View File
@@ -3919,7 +3919,7 @@ const message = {
alertCount: 'Contagem de Alertas',
clamHelper: 'Dispara alerta via ao detectar arquivos infectados durante a varredura',
cronJobHelper: 'Dispara alerta via ao falhar na execução de tarefas',
licenseHelper: 'A versão profissional suporta alertas via SMS',
licenseHelper: 'A versão profissional suporta alertas via',
alertCountHelper: 'Frequência máxima diária de alertas',
alert: 'Alerta por SMS',
logs: 'Registros de Alerta',
@@ -4124,6 +4124,13 @@ const message = {
panelLoginRule: 'Alerta de login no painel, enviado {0} vezes por dia',
sshLoginRule: 'Alerta de login SSH, enviado {0} vezes por dia',
userNameHelper: 'O nome de usuário está vazio, o endereço do remetente será usado por padrão',
alertConfigHelper: 'Configurar canais de notificação de alerta para receber push de mensagens do painel',
weComConfigHelper: 'Configuração de notificação de alerta WeCom',
wechatConfigHelper: 'Configuração de notificação de alerta da Conta Oficial WeChat',
dingTalkConfigHelper: 'Configuração de notificação de alerta DingTalk',
feiShuConfigHelper: 'Configuração de notificação de alerta Feishu',
webhookName: 'Nome do bot ou observação',
webhookUrl: 'URL do Webhook',
},
theme: {
lingXiaGold: 'Ling Xia Gold',
+8 -1
View File
@@ -3906,7 +3906,7 @@ const message = {
alertCount: 'Количество оповещений',
clamHelper: 'Отправлять оповещение при обнаружении зараженных файлов',
cronJobHelper: 'Отправлять оповещение при сбое выполнения задачи',
licenseHelper: 'Профессиональная версия поддерживает SMS-оповещения',
licenseHelper: 'Профессиональная версия поддерживает оповещения',
alertCountHelper: 'Максимальная дневная частота оповещений',
alert: 'SMS Уведомление',
logs: 'Журнал Уведомлений',
@@ -4115,6 +4115,13 @@ const message = {
panelLoginRule: 'Оповещение о входе в панель, отправляется {0} раз в день',
sshLoginRule: 'Оповещение о входе по SSH, отправляется {0} раз в день',
userNameHelper: 'Имя пользователя не указано, по умолчанию будет использоваться адрес отправителя',
alertConfigHelper: 'Настройка каналов уведомлений для получения push-уведомлений от панели',
weComConfigHelper: 'Конфигурация уведомлений WeCom',
wechatConfigHelper: 'Конфигурация уведомлений официального аккаунта WeChat',
dingTalkConfigHelper: 'Конфигурация уведомлений DingTalk',
feiShuConfigHelper: 'Конфигурация уведомлений Feishu',
webhookName: 'Имя бота или примечание',
webhookUrl: 'URL Webhook',
},
theme: {
lingXiaGold: 'Лин Ся Золотой',
+8 -1
View File
@@ -3978,7 +3978,7 @@ const message = {
alertCount: 'Uyarı Sayısı',
clamHelper: 'Enfekte dosyalar tarandığında uyarısını tetikle',
cronJobHelper: 'Görev yürütme başarısız olduğunda uyarısını tetikle',
licenseHelper: 'Profesyonel sürüm SMS uyarısını destekler',
licenseHelper: 'Profesyonel sürüm more uyarısını destekler',
alertCountHelper: 'Günlük maksimum uyarı sıklığı',
alert: 'SMS Uyarısı',
logs: 'Uyarı Günlükleri',
@@ -4185,6 +4185,13 @@ const message = {
panelLoginRule: 'Panel girişi uyarısı, günde {0} kez gönderilir',
sshLoginRule: 'SSH girişi uyarısı, günde {0} kez gönderilir',
userNameHelper: 'Kullanıcı adı boşsa, varsayılan olarak gönderici adresi kullanılacaktır',
alertConfigHelper: 'Panel mesaj gönderimini almak için uyarı bildirim kanallarını yapılandırın',
weComConfigHelper: 'WeCom uyarı bildirim yapılandırması',
wechatConfigHelper: 'WeChat Resmi Hesap uyarı bildirim yapılandırması',
dingTalkConfigHelper: 'DingTalk uyarı bildirim yapılandırması',
feiShuConfigHelper: 'Feishu uyarı bildirim yapılandırması',
webhookName: 'Bot adı veya not',
webhookUrl: 'Webhook URL',
},
theme: {
lingXiaGold: 'Ling Xia Altın',
+8 -1
View File
@@ -3630,7 +3630,7 @@ const message = {
alertCount: '告警次數',
clamHelper: '掃描到感染檔案時觸發告警',
cronJobHelper: '定時任務執行失敗時將觸發告警',
licenseHelper: '專業版支援簡訊告警功能',
licenseHelper: '專業版支援更多告警功能',
alertCountHelper: '每日最大告警次數',
alert: '簡訊告警',
logs: '告警日誌',
@@ -3824,6 +3824,13 @@ const message = {
panelLoginRule: '面板登入告警每天發送 {0} ',
sshLoginRule: 'SSH 登入告警每天發送 {0} ',
userNameHelper: '使用者名稱為空時將預設使用寄件者地址',
alertConfigHelper: '配置告警通知通道用於接收面板訊息推送',
weComConfigHelper: '企業微信告警通知配置',
wechatConfigHelper: '微信公眾號告警通知配置',
dingTalkConfigHelper: '釘釘告警通知配置',
feiShuConfigHelper: '飛書告警通知配置',
webhookName: '機器人名稱',
webhookUrl: 'Webhook 位址',
},
theme: {
lingXiaGold: '凌霞金',
+8 -1
View File
@@ -3624,7 +3624,7 @@ const message = {
alertCount: '告警次数',
clamHelper: '扫描到感染文件时触发告警通知',
cronJobHelper: '定时任务执行失败时将触发告警通知',
licenseHelper: '专业版支持短信告警功能',
licenseHelper: '专业版支持更多告警功能',
alertCountHelper: '每日最大告警次数',
alert: '短信告警',
logs: '告警日志',
@@ -3814,6 +3814,13 @@ const message = {
panelLoginRule: '面板登录告警每天发送 {0} ',
sshLoginRule: 'SSH 登录告警告警每天发送 {0} ',
userNameHelper: '用户名为空会默认使用发件箱地址',
alertConfigHelper: '配置告警通知通道用于接收面板消息推送',
weComConfigHelper: '企业微信告警通知配置',
wechatConfigHelper: '微信公众号告警通知配置',
dingTalkConfigHelper: '钉钉告警通知配置',
feiShuConfigHelper: '飞书告警通知配置',
webhookName: '机器人名称',
webhookUrl: 'Webhook 地址',
},
theme: {
lingXiaGold: '凌霞金',
@@ -722,6 +722,24 @@
:disabled="!form.hasAlert || !isProductPro"
:label="$t('xpack.alert.sms')"
/>
<el-option
value="weCom"
v-if="!globalStore.isIntl"
:disabled="!form.hasAlert || !isProductPro"
:label="$t('xpack.alert.weCom')"
/>
<el-option
value="dingTalk"
v-if="!globalStore.isIntl"
:disabled="!form.hasAlert || !isProductPro"
:label="$t('xpack.alert.dingTalk')"
/>
<el-option
value="feiShu"
v-if="!globalStore.isIntl"
:disabled="!form.hasAlert || !isProductPro"
:label="$t('xpack.alert.feiShu')"
/>
</el-select>
</el-form-item>
</LayoutCol>
@@ -328,6 +328,24 @@
:disabled="!globalStore.isProductPro"
:label="$t('xpack.alert.sms')"
/>
<el-option
value="weCom"
v-if="!globalStore.isIntl"
:disabled="!globalStore.isProductPro"
:label="$t('xpack.alert.weCom')"
/>
<el-option
value="dingTalk"
v-if="!globalStore.isIntl"
:disabled="!globalStore.isProductPro"
:label="$t('xpack.alert.dingTalk')"
/>
<el-option
value="feiShu"
v-if="!globalStore.isIntl"
:disabled="!globalStore.isProductPro"
:label="$t('xpack.alert.feiShu')"
/>
</el-select>
</el-form-item>
<span class="input-help">
+18 -1
View File
@@ -214,7 +214,24 @@ const formatMessage = (row: Alert.AlertInfo) => {
};
const formatMethod = (row: Alert.AlertLog) => {
return row.method === 'mail' ? t('xpack.alert.mail') : t('xpack.alert.sms');
switch (row.method) {
case 'mail':
return t('xpack.alert.mail');
case 'sms':
return t('xpack.alert.sms');
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 'webhook':
return t('xpack.alert.webhook');
default:
return t('xpack.alert.unknown');
}
};
const formatCount = (row: Alert.AlertInfo) => {
@@ -40,7 +40,22 @@
<LayoutContent :title="$t('commons.button.set')" v-loading="loading" :divider="true">
<template #title>{{ $t('xpack.alert.methodConfig') }}</template>
<template #main>
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3">
<el-alert type="info" :closable="false">
<template #title>
<div class="flex items-center justify-start">
{{ $t('xpack.alert.alertConfigHelper') }}
<el-link
class="ml-1 text-xs"
type="primary"
target="_blank"
:href="globalStore.docsUrl + '/user_manual/settings/#3'"
>
{{ $t('commons.button.helpDoc') }}
</el-link>
</div>
</template>
</el-alert>
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3 mt-3">
<el-card class="rounded-2xl shadow hover:shadow-md transition-all">
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">{{ $t('xpack.alert.emailConfig') }}</div>
@@ -67,7 +82,7 @@
</div>
<div class="text-sm mb-2">{{ $t('xpack.alert.emailConfigHelper') }}</div>
<el-divider class="!mb-2 !mt-3" />
<div class="text-sm email-form" v-if="emailConfig.id">
<div class="text-sm config-form" v-if="emailConfig.id">
<el-form
@submit.prevent
ref="alertFormRef"
@@ -117,14 +132,14 @@
</el-button>
</div>
</div>
<div class="text-sm mb-2">
<div class="text-sm mb-2 flex items-center justify-start">
{{ $t('xpack.alert.alertSmsHelper', [totalSms, usedSms]) }}
<el-link class="ml-1 text-xs" @click="goBuy" type="primary" icon="Position">
<span class="ml-0.5">{{ $t('xpack.alert.goBuy') }}</span>
</el-link>
</div>
<el-divider class="!mb-2 !mt-3" />
<div class="text-sm email-form">
<div class="text-sm config-form">
<el-form
@submit.prevent
ref="alertFormRef"
@@ -141,6 +156,156 @@
</el-form>
</div>
</el-card>
<el-card
class="rounded-2xl shadow hover:shadow-md transition-all"
v-if="globalStore.isProductPro && !globalStore.isIntl"
>
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">{{ $t('xpack.alert.weCom') }}</div>
<div>
<el-button
plain
round
size="default"
:disabled="!weComConfig.id"
@click="onChangeWeCom(weComConfig.id)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button
size="default"
plain
round
:disabled="!weComConfig.id"
@click="onDelete(weComConfig.id)"
>
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<div class="text-sm mb-2">{{ $t('xpack.alert.weComConfigHelper') }}</div>
<el-divider class="!mb-2 !mt-3" />
<div class="text-sm config-form" v-if="weComConfig.id">
<el-form
@submit.prevent
ref="alertFormRef"
:label-position="mobile ? 'top' : 'left'"
label-width="110px"
>
<el-form-item :label="$t('xpack.alert.webhookName')" prop="displayName">
{{ weComConfig.config.displayName }}
</el-form-item>
<el-form-item :label="$t('xpack.alert.webhookUrl')" prop="url">
{{ weComConfig.config.url }}
</el-form-item>
</el-form>
</div>
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onChangeWeCom(0)">
{{ $t('commons.button.create') }}{{ $t('xpack.alert.weCom') }}
</el-button>
</el-alert>
</el-card>
<el-card
class="rounded-2xl shadow hover:shadow-md transition-all"
v-if="globalStore.isProductPro && !globalStore.isIntl"
>
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">{{ $t('xpack.alert.dingTalk') }}</div>
<div>
<el-button
plain
round
size="default"
:disabled="!dingTalkConfig.id"
@click="onChangeDingTalk(dingTalkConfig.id)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button
size="default"
plain
round
:disabled="!dingTalkConfig.id"
@click="onDelete(dingTalkConfig.id)"
>
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<div class="text-sm mb-2">{{ $t('xpack.alert.dingTalkConfigHelper') }}</div>
<el-divider class="!mb-2 !mt-3" />
<div class="text-sm config-form" v-if="dingTalkConfig.id">
<el-form
@submit.prevent
ref="alertFormRef"
:label-position="mobile ? 'top' : 'left'"
label-width="110px"
>
<el-form-item :label="$t('xpack.alert.webhookName')" prop="displayName">
{{ dingTalkConfig.config.displayName }}
</el-form-item>
<el-form-item :label="$t('xpack.alert.webhookUrl')" prop="url">
{{ dingTalkConfig.config.url }}
</el-form-item>
</el-form>
</div>
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onChangeDingTalk(0)">
{{ $t('commons.button.create') }}{{ $t('xpack.alert.dingTalk') }}
</el-button>
</el-alert>
</el-card>
<el-card
class="rounded-2xl shadow hover:shadow-md transition-all"
v-if="globalStore.isProductPro && !globalStore.isIntl"
>
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">{{ $t('xpack.alert.feiShu') }}</div>
<div>
<el-button
plain
round
size="default"
:disabled="!feiShuConfig.id"
@click="onChangeDingTalk(feiShuConfig.id)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button
size="default"
plain
round
:disabled="!feiShuConfig.id"
@click="onDelete(feiShuConfig.id)"
>
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<div class="text-sm mb-2">{{ $t('xpack.alert.feiShuConfigHelper') }}</div>
<el-divider class="!mb-2 !mt-3" />
<div class="text-sm config-form" v-if="feiShuConfig.id">
<el-form
@submit.prevent
ref="alertFormRef"
:label-position="mobile ? 'top' : 'left'"
label-width="110px"
>
<el-form-item :label="$t('xpack.alert.webhookName')" prop="displayName">
{{ feiShuConfig.config.displayName }}
</el-form-item>
<el-form-item :label="$t('xpack.alert.webhookUrl')" prop="url">
{{ feiShuConfig.config.url }}
</el-form-item>
</el-form>
</div>
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button size="large" round plain type="primary" @click="onChangeFeiShu(0)">
{{ $t('commons.button.create') }}{{ $t('xpack.alert.feiShu') }}
</el-button>
</el-alert>
</el-card>
</div>
</template>
</LayoutContent>
@@ -148,6 +313,7 @@
<EmailDrawer ref="emailRef" @search="search" />
<Phone ref="phoneRef" @search="search" />
<SendTimeRange ref="sendTimeRangeRef" @search="search" />
<WebhookDrawer ref="webHookRef" @search="search" />
</div>
</template>
@@ -162,6 +328,7 @@ import i18n from '@/lang';
import { storeToRefs } from 'pinia';
import { MsgSuccess } from '@/utils/message';
import EmailDrawer from '@/views/setting/alert/setting/email/index.vue';
import WebhookDrawer from '@/views/setting/alert/setting/webhook/index.vue';
import { Alert } from '@/api/interface/alert';
import { getLicenseSmsInfo } from '@/api/modules/setting';
@@ -172,29 +339,13 @@ const loading = ref(false);
const alertFormRef = ref<FormInstance>();
const phoneRef = ref();
const emailRef = ref();
const webHookRef = ref();
const sendTimeRangeRef = ref();
const sendTimeRangeValue = ref();
const sendTimeRange = ref();
const isInitialized = ref(false);
export interface EmailConfig {
id?: number;
type: string;
title: string;
status: string;
config: {
status?: string;
sender?: string;
userName?: string;
password?: string;
displayName?: string;
host?: string;
port?: number;
encryption?: string;
recipient?: string;
};
}
const defaultEmailConfig: EmailConfig = {
const defaultEmailConfig: Alert.EmailConfig = {
id: undefined,
type: 'email',
title: 'xpack.alert.emailConfig',
@@ -211,19 +362,9 @@ const defaultEmailConfig: EmailConfig = {
recipient: '',
},
};
const emailConfig = ref<EmailConfig>({ ...defaultEmailConfig });
const emailConfig = ref<Alert.EmailConfig>({ ...defaultEmailConfig });
export interface CommonConfig {
id?: number;
type: string;
title: string;
status: string;
config: {
isOffline?: string;
alertSendTimeRange?: string;
};
}
const defaultCommonConfig: CommonConfig = {
const defaultCommonConfig: Alert.CommonConfig = {
id: undefined,
type: 'common',
title: 'xpack.alert.commonConfig',
@@ -241,19 +382,9 @@ const defaultCommonConfig: CommonConfig = {
},
};
const commonConfig = ref<CommonConfig>({ ...defaultCommonConfig });
const commonConfig = ref<Alert.CommonConfig>({ ...defaultCommonConfig });
export interface SmsConfig {
id?: number;
type: string;
title: string;
status: string;
config: {
phone?: string;
alertDailyNum?: number;
};
}
const defaultSmsConfig: SmsConfig = {
const defaultSmsConfig: Alert.SmsConfig = {
id: undefined,
type: 'sms',
title: 'xpack.alert.smsConfig',
@@ -263,7 +394,43 @@ const defaultSmsConfig: SmsConfig = {
alertDailyNum: 50,
},
};
const smsConfig = ref<SmsConfig>({ ...defaultSmsConfig });
const smsConfig = ref<Alert.SmsConfig>({ ...defaultSmsConfig });
const defaultWeComConfig: Alert.WebhookConfig = {
id: undefined,
type: 'weCom',
title: 'xpack.alert.weCom',
status: 'Enable',
config: {
displayName: '',
url: '',
},
};
const weComConfig = ref<Alert.WebhookConfig>({ ...defaultWeComConfig });
const defaultDingTalkConfig: Alert.WebhookConfig = {
id: undefined,
type: 'dingTalk',
title: 'xpack.alert.dingTalk',
status: 'Enable',
config: {
displayName: '',
url: '',
},
};
const dingTalkConfig = ref<Alert.WebhookConfig>({ ...defaultDingTalkConfig });
const defaultFeiShuConfig: Alert.WebhookConfig = {
id: undefined,
type: 'feiShu',
title: 'xpack.alert.feiShu',
status: 'Enable',
config: {
displayName: '',
url: '',
},
};
const feiShuConfig = ref<Alert.WebhookConfig>({ ...defaultFeiShuConfig });
const config = ref<Alert.AlertConfigInfo>({
id: 0,
@@ -329,6 +496,15 @@ const search = async () => {
i18n.global.t('xpack.alert.resourceAlert') +
': ' +
resourceTimeRange;
const weComFound = res.data.find((s: any) => s.type === 'weCom');
assignConfig(weComFound, weComConfig, defaultWeComConfig);
const dingTalkFound = res.data.find((s: any) => s.type === 'dingTalk');
assignConfig(dingTalkFound, dingTalkConfig, defaultDingTalkConfig);
const feiShuFound = res.data.find((s: any) => s.type === 'feiShu');
assignConfig(feiShuFound, feiShuConfig, defaultFeiShuConfig);
isInitialized.value = true;
} finally {
loading.value = false;
@@ -416,6 +592,33 @@ const goBuy = async () => {
window.open('https://www.lxware.cn/uc/cloud/licenses/' + uri, '_blank', 'noopener,noreferrer');
};
const onChangeWeCom = (id: number) => {
webHookRef.value.acceptParams({
id: id,
config: weComConfig.value.config,
type: 'weCom',
title: weComConfig.value.title,
});
};
const onChangeDingTalk = (id: number) => {
webHookRef.value.acceptParams({
id: id,
config: dingTalkConfig.value.config,
type: 'dingTalk',
title: dingTalkConfig.value.title,
});
};
const onChangeFeiShu = (id: number) => {
webHookRef.value.acceptParams({
id: id,
config: feiShuConfig.value.config,
type: 'feiShu',
title: feiShuConfig.value.title,
});
};
onMounted(async () => {
await search();
if (globalStore.isProductPro && !globalStore.isIntl) {
@@ -427,7 +630,7 @@ onMounted(async () => {
.label {
color: var(--el-text-color-placeholder);
}
.email-form {
.config-form {
.el-form-item {
margin-bottom: 0 !important;
}
@@ -0,0 +1,113 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('xpack.alert.' + form.type)" @close="handleClose" size="736">
<el-form
ref="formRef"
:rules="rules"
label-position="top"
:model="form.config"
@submit.prevent
v-loading="loading"
>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('xpack.alert.webhookName')" prop="displayName">
<el-input v-model="form.config.displayName" />
</el-form-item>
<el-form-item :label="$t('xpack.alert.webhookUrl')" prop="url">
<el-input v-model.trim="form.config.url" :rows="2" type="password" show-password />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="flex items-center justify-between">
<div>
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</div>
</div>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { UpdateAlertConfig } from '@/api/modules/alert';
import { Rules } from '@/global/form-rules';
const emit = defineEmits<{ (e: 'search'): void }>();
const rules = {
displayName: [Rules.requiredInput],
url: [Rules.requiredInput],
};
interface Config {
displayName: string;
url: string;
}
interface DialogProps {
id: number;
type: string;
title: string;
config: Config;
}
const drawerVisible = ref();
const loading = ref();
const form = reactive({
id: undefined,
type: '',
title: '',
config: {
displayName: '',
url: '',
},
});
const formRef = ref<FormInstance>();
const acceptParams = (params: DialogProps): void => {
form.id = params.id;
form.type = params.type;
form.title = params.title;
form.config = params.config;
drawerVisible.value = true;
};
const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
try {
const configInfo = form.config;
await UpdateAlertConfig({
id: form.id,
type: form.type,
title: form.title,
status: 'Enable',
config: JSON.stringify(configInfo),
});
loading.value = false;
handleClose();
emit('search');
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
} catch (error) {
loading.value = false;
}
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>
@@ -160,6 +160,24 @@
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
:label="$t('xpack.alert.sms')"
/>
<el-option
value="weCom"
v-if="!globalStore.isIntl"
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
:label="$t('xpack.alert.weCom')"
/>
<el-option
value="dingTalk"
v-if="!globalStore.isIntl"
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
:label="$t('xpack.alert.dingTalk')"
/>
<el-option
value="feiShu"
v-if="!globalStore.isIntl"
:disabled="!dialogData.rowData!.hasAlert || !isProductPro"
:label="$t('xpack.alert.feiShu')"
/>
</el-select>
</el-form-item>
<el-form-item