mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-11-07 10:16:21 +08:00
feat(alert): Support panel login/ssh login/license exception and node exception alerts (#10034)
#9635
This commit is contained in:
parent
4e6ebb53ac
commit
0dca3fc997
34 changed files with 1369 additions and 691 deletions
|
|
@ -36,40 +36,43 @@ type AlertSearch struct {
|
|||
}
|
||||
|
||||
type AlertDTO struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Cycle uint `json:"cycle"`
|
||||
Count uint `json:"count"`
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Cycle uint `json:"cycle"`
|
||||
Count uint `json:"count"`
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AlertCreate struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Cycle uint `json:"cycle"`
|
||||
Count uint `json:"count"`
|
||||
Method string `json:"method" validate:"required"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Cycle uint `json:"cycle"`
|
||||
Count uint `json:"count"`
|
||||
Method string `json:"method" validate:"required"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
}
|
||||
|
||||
type AlertUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Type string `json:"type"`
|
||||
Cycle uint `json:"cycle"`
|
||||
Count uint `json:"count"`
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Type string `json:"type"`
|
||||
Cycle uint `json:"cycle"`
|
||||
Count uint `json:"count"`
|
||||
Method string `json:"method"`
|
||||
Title string `json:"title"`
|
||||
Project string `json:"project"`
|
||||
Status string `json:"status"`
|
||||
SendCount uint `json:"sendCount"`
|
||||
AdvancedParams string `json:"advancedParams"`
|
||||
}
|
||||
|
||||
type DeleteRequest struct {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ package model
|
|||
type Alert struct {
|
||||
BaseModel
|
||||
|
||||
Title string `gorm:"type:varchar(256);not null" json:"title"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Cycle uint `gorm:"type:integer;not null" json:"cycle"`
|
||||
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"`
|
||||
SendCount uint `gorm:"type:integer" json:"sendCount"`
|
||||
Title string `gorm:"type:varchar(256);not null" json:"title"`
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Cycle uint `gorm:"type:integer;not null" json:"cycle"`
|
||||
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"`
|
||||
SendCount uint `gorm:"type:integer" json:"sendCount"`
|
||||
AdvancedParams string `gorm:"type:longText" json:"advancedParam"`
|
||||
}
|
||||
|
||||
type AlertTask struct {
|
||||
|
|
@ -43,3 +44,12 @@ type AlertConfig struct {
|
|||
Status string `gorm:"type:varchar(64);not null" json:"status"`
|
||||
Config string `gorm:"type:varchar(256);not null" json:"config"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
BaseModel
|
||||
IP string `json:"ip"`
|
||||
Address string `json:"address"`
|
||||
Agent string `json:"agent"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,17 +70,18 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
|
|||
for _, item := range alerts {
|
||||
|
||||
result = append(result, dto.AlertDTO{
|
||||
ID: item.ID,
|
||||
Type: item.Type,
|
||||
Cycle: item.Cycle,
|
||||
Count: item.Count,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
ID: item.ID,
|
||||
Type: item.Type,
|
||||
Cycle: item.Cycle,
|
||||
Count: item.Count,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -100,17 +101,18 @@ func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
|
|||
for _, item := range alerts {
|
||||
|
||||
result = append(result, dto.AlertDTO{
|
||||
ID: item.ID,
|
||||
Type: item.Type,
|
||||
Cycle: item.Cycle,
|
||||
Count: item.Count,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
ID: item.ID,
|
||||
Type: item.Type,
|
||||
Cycle: item.Cycle,
|
||||
Count: item.Count,
|
||||
Method: item.Method,
|
||||
Title: item.Title,
|
||||
Project: item.Project,
|
||||
Status: item.Status,
|
||||
SendCount: item.SendCount,
|
||||
AdvancedParams: item.AdvancedParams,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +167,7 @@ func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
|
|||
upMap["project"] = req.Project
|
||||
upMap["status"] = req.Status
|
||||
upMap["send_count"] = req.SendCount
|
||||
upMap["advanced_params"] = req.AdvancedParams
|
||||
|
||||
if err := alertRepo.Update(upMap, repo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
165
agent/app/service/alert_sender.go
Normal file
165
agent/app/service/alert_sender.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"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 {
|
||||
alert dto.AlertDTO
|
||||
quotaType string
|
||||
}
|
||||
|
||||
func NewAlertSender(alert dto.AlertDTO, quotaType string) *AlertSender {
|
||||
return &AlertSender{
|
||||
alert: alert,
|
||||
quotaType: quotaType,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
_ = xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
|
||||
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) {
|
||||
totalCount, isValid := s.canSendAlert(constant.Email)
|
||||
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.Email),
|
||||
}
|
||||
|
||||
transport := xpack.LoadRequestTransport()
|
||||
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport)
|
||||
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
|
||||
global.LOG.Infof("%s alert email push successful", s.alert.Type)
|
||||
}
|
||||
|
||||
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
return
|
||||
}
|
||||
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.SMS)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: todayCount + 1,
|
||||
AlertId: s.alert.ID,
|
||||
Type: s.alert.Type,
|
||||
}
|
||||
|
||||
if err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS); err != nil {
|
||||
global.LOG.Errorf("failed to send SMS alert: %v", 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) sendResourceEmail(quota string, params []dto.Param) {
|
||||
todayCount, isValid := s.canResourceSendAlert(constant.Email)
|
||||
if !isValid {
|
||||
return
|
||||
}
|
||||
|
||||
create := dto.AlertLogCreate{
|
||||
Status: constant.AlertSuccess,
|
||||
Count: todayCount + 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.LoadRequestTransport()
|
||||
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport); err != nil {
|
||||
global.LOG.Errorf("failed to send Email alert: %v", 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) canSendAlert(method string) (uint, bool) {
|
||||
todayCount, totalCount, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting task count: %v", err)
|
||||
return totalCount, false
|
||||
}
|
||||
|
||||
if todayCount >= 1 || s.alert.SendCount <= totalCount {
|
||||
return totalCount, false
|
||||
}
|
||||
return totalCount, true
|
||||
}
|
||||
|
||||
func (s *AlertSender) canResourceSendAlert(method string) (uint, bool) {
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting task count: %v", err)
|
||||
return todayCount, false
|
||||
}
|
||||
if s.alert.SendCount <= todayCount {
|
||||
return todayCount, false
|
||||
}
|
||||
return todayCount, true
|
||||
}
|
||||
|
|
@ -449,4 +449,8 @@ SSLAlert: "There are {{ .num }} SSL certificates on your 1Panel that will expire
|
|||
DiskUsedAlert: "Your 1Panel disk '{{ .name }}' has used {{ .used }}. Please log in to the panel for details."
|
||||
ResourceAlert: "The average {{ .name }} usage over {{ .time }} minutes on your 1Panel is {{ .used }}. Please log in to the panel for details."
|
||||
PanelVersionAlert: "A new version of 1Panel is available. Please log in to the panel to upgrade."
|
||||
PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. Please log in to the panel for details."
|
||||
PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. Please log in to the panel for details."
|
||||
CommonAlert: "Your 1Panel, {{ .msg }}, please log in to the panel for details."
|
||||
NodeExceptionAlert: "Your 1Panel, {{ .num }} nodes are abnormal, please log in to the panel for details."
|
||||
LicenseExceptionAlert: "Your 1Panel, {{ .num }} licenses are abnormal, please log in to the panel for details."
|
||||
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
|
||||
|
|
@ -449,4 +449,8 @@ SSLAlert: "1Panel にある {{ .num }} 枚のSSL証明書が {{ .day }} 日後
|
|||
DiskUsedAlert: "1Panel のディスク '{{ .name }}' は {{ .used }} 使用されています。詳細はパネルでご確認ください。"
|
||||
ResourceAlert: "1Panel の {{ .time }} 分間の平均 {{ .name }} 使用率は {{ .used }} です。詳細はパネルでご確認ください。"
|
||||
PanelVersionAlert: "1Panel に新しいバージョンが利用可能です。アップグレードはパネルから行ってください。"
|
||||
PanelPwdExpirationAlert: "1Panel のパスワードは {{ .day }} 日後に期限切れとなります。詳細はパネルでご確認ください。"
|
||||
PanelPwdExpirationAlert: "1Panel のパスワードは {{ .day }} 日後に期限切れとなります。詳細はパネルでご確認ください。"
|
||||
CommonAlert: "お使いの1Panel、{{ .msg }}、詳細はパネルにログインしてご確認ください。"
|
||||
NodeExceptionAlert: "お使いの1Panel、{{ .num }}個のノードに異常があります。詳細はパネルにログインしてご確認ください。"
|
||||
LicenseExceptionAlert: "お使いの1Panel、{{ .num }}個のライセンスに異常があります。詳細はパネルにログインしてご確認ください。"
|
||||
SSHAndPanelLoginAlert: "お使いの1Panel、パネル{{ .name }}が{{ .ip }}から異常ログインしました。詳細はパネルにログインしてご確認ください。"
|
||||
|
|
@ -449,4 +449,8 @@ SSLAlert: "1Panel 에 있는 {{ .num }}개의 SSL 인증서가 {{ .day }}일 후
|
|||
DiskUsedAlert: "1Panel 디스크 '{{ .name }}'의 사용량은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
|
||||
ResourceAlert: "1Panel 의 평균 {{ .time }}분 동안 {{ .name }} 사용률은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
|
||||
PanelVersionAlert: "1Panel 의 새로운 버전이 이용 가능합니다. 패널에 로그인하여 업그레이드하세요."
|
||||
PanelPwdExpirationAlert: "1Panel 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
|
||||
PanelPwdExpirationAlert: "1Panel 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
|
||||
CommonAlert: "귀하의 1Panel, {{ .msg }}. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
NodeExceptionAlert: "귀하의 1Panel, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
LicenseExceptionAlert: "귀하의 1Panel, {{ .num }}개의 라이선스에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
SSHAndPanelLoginAlert: "귀하의 1Panel, 패널 {{ .name }}이(가) {{ .ip }}에서 비정상 로그인했습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
|
|
@ -448,4 +448,8 @@ SSLAlert: "Terdapat {{ .num }} sijil SSL dalam 1Panel anda yang akan tamat dalam
|
|||
DiskUsedAlert: "Cakera '{{ .name }}' dalam 1Panel anda telah menggunakan {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
|
||||
ResourceAlert: "Penggunaan purata {{ .name }} selama {{ .time }} minit dalam 1Panel anda ialah {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
|
||||
PanelVersionAlert: "Versi baru 1Panel tersedia. Sila log masuk ke panel untuk menaik taraf."
|
||||
PanelPwdExpirationAlert: "Kata laluan 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
|
||||
PanelPwdExpirationAlert: "Kata laluan 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
|
||||
CommonAlert: "1Panel anda, {{ .msg }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
NodeExceptionAlert: "1Panel anda, {{ .num }} nod bermasalah, sila log masuk ke panel untuk maklumat lanjut."
|
||||
LicenseExceptionAlert: "1Panel anda, {{ .num }} lesen bermasalah, sila log masuk ke panel untuk maklumat lanjut."
|
||||
SSHAndPanelLoginAlert: "1Panel anda, log masuk panel {{ .name }} yang tidak normal dari {{ .ip }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
|
|
@ -449,4 +449,8 @@ SSLAlert: "{{ .num }} certificados SSL do seu 1Panel expirarão em {{ .day }} di
|
|||
DiskUsedAlert: "O disco '{{ .name }}' do seu 1Panel está com uso de {{ .used }}. Acesse o painel para mais detalhes."
|
||||
ResourceAlert: "O uso médio de {{ .name }} em {{ .time }} minutos no seu 1Panel é de {{ .used }}. Acesse o painel para mais detalhes."
|
||||
PanelVersionAlert: "Uma nova versão do 1Panel está disponível. Acesse o painel para atualizá-lo."
|
||||
PanelPwdExpirationAlert: "A senha do 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
|
||||
PanelPwdExpirationAlert: "A senha do 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
|
||||
CommonAlert: "Seu 1Panel, {{ .msg }}. Para mais detalhes, faça login no painel."
|
||||
NodeExceptionAlert: "Seu 1Panel, {{ .num }} nós estão com problemas. Para mais detalhes, faça login no painel."
|
||||
LicenseExceptionAlert: "Seu 1Panel, {{ .num }} licenças estão com problemas. Para mais detalhes, faça login no painel."
|
||||
SSHAndPanelLoginAlert: "Seu 1Panel, login anormal no painel {{ .name }} a partir de {{ .ip }}. Para mais detalhes, faça login no painel."
|
||||
|
|
@ -449,4 +449,8 @@ SSLAlert: "На вашем 1Panel {{ .num }} SSL-сертификатов ист
|
|||
DiskUsedAlert: "Диск '{{ .name }}' на вашем 1Panel использует {{ .used }}. Подробности смотрите в панели."
|
||||
ResourceAlert: "Средняя загрузка {{ .name }} за {{ .time }} минут составляет {{ .used }}. Подробности смотрите в панели."
|
||||
PanelVersionAlert: "Доступна новая версия 1Panel. Обновитесь через панель."
|
||||
PanelPwdExpirationAlert: "Пароль для 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
|
||||
PanelPwdExpirationAlert: "Пароль для 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
|
||||
CommonAlert: "Ваш 1Panel, {{ .msg }}. Подробности смотрите, войдя в панель."
|
||||
NodeExceptionAlert: "Ваш 1Panel, {{ .num }} узлов работают неправильно. Подробности смотрите, войдя в панель."
|
||||
LicenseExceptionAlert: "Ваш 1Panel, {{ .num }} лицензий работают неправильно. Подробности смотрите, войдя в панель."
|
||||
SSHAndPanelLoginAlert: "Ваш 1Panel, обнаружен аномальный вход в панель {{ .name }} с {{ .ip }}. Подробности смотрите, войдя в панель."
|
||||
|
|
@ -448,3 +448,7 @@ DiskUsedAlert: "1Panel diski '{{ .name }}' {{ .used }} kullanıldı. Detaylar i
|
|||
ResourceAlert: "1Panel üzerinde ortalama {{ .time }} dakikalık {{ .name }} kullanım oranı {{ .used }}. Detaylar için panele giriş yapın."
|
||||
PanelVersionAlert: "1Panel için yeni bir sürüm mevcut. Güncellemek için panele giriş yapın."
|
||||
PanelPwdExpirationAlert: "1Panel şifreniz {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
|
||||
CommonAlert: "1Panel'iniz, {{ .msg }}. Detaylar için panele giriş yapınız."
|
||||
NodeExceptionAlert: "1Panel'iniz, {{ .num }} düğümde sorun var. Detaylar için panele giriş yapınız."
|
||||
LicenseExceptionAlert: "1Panel'iniz, {{ .num }} lisansda sorun var. Detaylar için panele giriş yapınız."
|
||||
SSHAndPanelLoginAlert: "1Panel'iniz, {{ .ip }} adresinden {{ .name }} paneline anormal giriş tespit edildi. Detaylar için panele giriş yapınız."
|
||||
|
|
|
|||
|
|
@ -449,3 +449,7 @@ DiskUsedAlert: "您的 1Panel 面板磁碟 '{{ .name }}' 已使用 {{ .used }}
|
|||
ResourceAlert: "您的 1Panel 面板於 {{ .time }} 分鐘內的平均 {{ .name }} 使用率為 {{ .used }},詳情請登入面板查看。"
|
||||
PanelVersionAlert: "您的 1Panel 面板有可升級的新版本,詳情請登入面板查看。"
|
||||
PanelPwdExpirationAlert: "您的 1Panel 面板密碼將於 {{ .day }} 天後到期,詳情請登入面板查看。"
|
||||
CommonAlert: "您的 1Panel 面板,{{ .msg }},詳情請登入面板查看。"
|
||||
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 個節點存在異常,詳情請登入面板查看。"
|
||||
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 個許可證存在異常,詳情請登入面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}從 {{ .ip }} 登錄異常,詳情請登入面板查看。"
|
||||
|
|
|
|||
|
|
@ -449,4 +449,8 @@ SSLAlert: "您的 1Panel 面板,有 {{ .num }} 张SSL证书将在 {{ .day }}
|
|||
DiskUsedAlert: "您的 1Panel 面板,磁盘 {{ .name }} 已使用 {{ .used }},详情请登录面板查看。"
|
||||
ResourceAlert: "您的 1Panel 面板,平均 {{ .time }} 分钟内的 {{ .name }} 使用率为 {{ .used }},详情请登录面板查看。"
|
||||
PanelVersionAlert: "您的 1Panel 面板,有最新面板版本可供升级,详情请登录面板查看。"
|
||||
PanelPwdExpirationAlert: "您的 1Panel 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。"
|
||||
PanelPwdExpirationAlert: "您的 1Panel 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。"
|
||||
CommonAlert: "您的 1Panel 面板,{{ .msg }},详情请登录面板查看。"
|
||||
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。"
|
||||
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}登录{{ .ip }}异常,详情请登录面板查看。"
|
||||
|
|
@ -35,6 +35,7 @@ func InitAgentDB() {
|
|||
migrations.AddMethodToAlertTask,
|
||||
migrations.UpdateMcpServer,
|
||||
migrations.InitCronjobGroup,
|
||||
migrations.AddColumnToAlert,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
|||
|
|
@ -446,3 +446,18 @@ var InitCronjobGroup = &gormigrate.Migration{
|
|||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddColumnToAlert = &gormigrate.Migration{
|
||||
ID: "20250729-add-column-to-alert",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := global.AlertDB.AutoMigrate(&model.Alert{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := global.AlertDB.Model(&model.Alert{}).
|
||||
Where("advanced_params IS NULL").
|
||||
Update("advanced_params", "").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,24 @@ package alert
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/email"
|
||||
"github.com/jinzhu/copier"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"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"}
|
||||
|
|
@ -66,7 +69,7 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
|
|||
Encryption: emailInfo.Encryption,
|
||||
Recipient: emailInfo.Recipient,
|
||||
}
|
||||
content := alert.Title
|
||||
content := i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
|
||||
if GetEmailContent(alert.Type, params) != "" {
|
||||
content = GetEmailContent(alert.Type, params)
|
||||
}
|
||||
|
|
@ -191,7 +194,7 @@ func CreateAlertParams(param string) []dto.Param {
|
|||
|
||||
var checkTaskMutex sync.Mutex
|
||||
|
||||
func CheckTaskFrequency(method string) bool {
|
||||
func CheckSMSSendLimit(method string) bool {
|
||||
alertRepo := repo.NewIAlertRepo()
|
||||
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
|
||||
if err != nil {
|
||||
|
|
@ -204,8 +207,8 @@ func CheckTaskFrequency(method string) bool {
|
|||
}
|
||||
limitCount := cfg.AlertDailyNum
|
||||
checkTaskMutex.Lock()
|
||||
todayCount, err := alertRepo.GetLicensePushCount(method)
|
||||
defer checkTaskMutex.Unlock()
|
||||
todayCount, err := alertRepo.GetLicensePushCount(method)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("error getting license push count info, err: %v", err)
|
||||
return false
|
||||
|
|
@ -312,6 +315,18 @@ func GetEmailContent(alertType string, params []dto.Param) string {
|
|||
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1")})
|
||||
case "clams":
|
||||
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
|
||||
case "panelLogin":
|
||||
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
|
||||
case "sshLogin":
|
||||
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
|
||||
case "panelIpLogin":
|
||||
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
|
||||
case "sshIpLogin":
|
||||
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
|
||||
case "nodeException":
|
||||
return i18n.GetMsgWithMap("NodeExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
|
||||
case "licenseException":
|
||||
return i18n.GetMsgWithMap("LicenseExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
@ -325,3 +340,162 @@ func getValueByIndex(params []dto.Param, index string) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func CountRecentFailedLoginLogs(minutes uint, failCount uint) (int, bool, error) {
|
||||
now := time.Now()
|
||||
startTime := now.Add(-time.Duration(minutes) * time.Minute)
|
||||
db := global.CoreDB.Model(&model.LoginLog{})
|
||||
var count int64
|
||||
err := db.Where("created_at >= ? AND status = ?", startTime, constant.StatusFailed).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
return int(count), int(count) > int(failCount), nil
|
||||
}
|
||||
|
||||
func FindRecentSuccessLoginsNotInWhitelist(minutes int, whitelist []string) ([]model.LoginLog, error) {
|
||||
now := time.Now()
|
||||
startTime := now.Add(-time.Duration(minutes) * time.Minute)
|
||||
|
||||
whitelistMap := make(map[string]struct{})
|
||||
for _, ip := range whitelist {
|
||||
whitelistMap[ip] = struct{}{}
|
||||
}
|
||||
|
||||
var logs []model.LoginLog
|
||||
err := global.CoreDB.Model(&model.LoginLog{}).
|
||||
Where("created_at >= ? AND status = ?", startTime, constant.StatusSuccess).
|
||||
Find(&logs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var abnormalLogs []model.LoginLog
|
||||
for _, log := range logs {
|
||||
if _, ok := whitelistMap[log.IP]; !ok {
|
||||
abnormalLogs = append(abnormalLogs, log)
|
||||
}
|
||||
}
|
||||
return abnormalLogs, nil
|
||||
}
|
||||
|
||||
func CountRecentFailedSSHLog(minutes uint, maxAllowed uint) (int, bool, error) {
|
||||
lines, err := grepSSHLog("Failed password")
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
thresholdTime := time.Now().Add(-time.Duration(minutes) * time.Minute)
|
||||
count := 0
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
t, err := parseLogTime(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if t.After(thresholdTime) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count, count > int(maxAllowed), nil
|
||||
}
|
||||
|
||||
func FindRecentSuccessLoginNotInWhitelist(minutes int, whitelist []string) ([]string, error) {
|
||||
lines, err := grepSSHLog("Accepted password")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
thresholdTime := time.Now().Add(-time.Duration(minutes) * time.Minute)
|
||||
var abnormalLogins []string
|
||||
|
||||
whitelistMap := make(map[string]struct{}, len(whitelist))
|
||||
for _, ip := range whitelist {
|
||||
whitelistMap[ip] = struct{}{}
|
||||
}
|
||||
|
||||
ipRegex := regexp.MustCompile(`from\s+([0-9.]+)\s+port\s+(\d+)`)
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := parseLogTime(line)
|
||||
if err != nil || t.Before(thresholdTime) {
|
||||
continue
|
||||
}
|
||||
|
||||
match := ipRegex.FindStringSubmatch(line)
|
||||
if len(match) >= 2 {
|
||||
ip := match[1]
|
||||
if _, ok := whitelistMap[ip]; !ok {
|
||||
abnormalLogins = append(abnormalLogins, fmt.Sprintf("%s-%s", ip, t.Format("2006-01-02 15:04:05")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return abnormalLogins, nil
|
||||
}
|
||||
|
||||
func findGrepPath() (string, error) {
|
||||
path, err := exec.LookPath("grep")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("grep not found in PATH: %w", err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func grepSSHLog(keyword string) ([]string, error) {
|
||||
logFiles := []string{"/var/log/secure", "/var/log/auth.log"}
|
||||
var results []string
|
||||
grepPath, err := findGrepPath()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, logFile := range logFiles {
|
||||
if _, err := os.Stat(logFile); err != nil {
|
||||
continue
|
||||
}
|
||||
cmd := exec.Command(grepPath, "-a", keyword, logFile)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("read log file fail [%s]: %w", logFile, err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
results = append(results, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func parseLogTime(line string) (time.Time, error) {
|
||||
if len(line) < 15 {
|
||||
return time.Time{}, errors.New("log line time is incorrect")
|
||||
}
|
||||
timeStr := line[:15]
|
||||
parsedTime, err := time.ParseInLocation("Jan 2 15:04:05", timeStr, time.Local)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return parsedTime.AddDate(time.Now().Year(), 0, 0), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func PushAlert(pushAlert dto.PushAlert) error {
|
|||
m = strings.TrimSpace(m)
|
||||
switch m {
|
||||
case constant.SMS:
|
||||
if !alertUtil.CheckTaskFrequency(constant.SMS) {
|
||||
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
|
||||
continue
|
||||
}
|
||||
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS)
|
||||
|
|
@ -43,7 +43,7 @@ func PushAlert(pushAlert dto.PushAlert) error {
|
|||
AlertId: alert.ID,
|
||||
Count: todayCount + 1,
|
||||
}
|
||||
_ = xpack.CreateTaskScanSMSAlertLog(alert, create, pushAlert, constant.SMS)
|
||||
_ = xpack.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -53,14 +53,22 @@ func IsUseCustomApp() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func CreateTaskScanSMSAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
|
||||
func CreateTaskScanSMSAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateSMSAlertLog(info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
|
||||
func CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLicenseErrorAlert() (uint, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func GetNodeErrorAlert() (uint, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func LoadRequestTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export namespace Alert {
|
|||
status: string;
|
||||
sendCount: number;
|
||||
sendMethod: string[];
|
||||
advancedParams: string;
|
||||
}
|
||||
|
||||
export interface AlertDetail {
|
||||
|
|
|
|||
|
|
@ -3583,7 +3583,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: 'The average cpu usage within the specified time exceeds the specified value',
|
||||
memoryUseExceedAvgHelper: 'The average memory usage within the specified time exceeds the specified value',
|
||||
loadUseExceedAvgHelper: 'The average load usage within the specified time exceeds the specified value',
|
||||
resourceAlertRulesHelper: 'Note: Continuous alerts within 30 minutes will send only one SMS',
|
||||
resourceAlertRulesHelper: 'Note: Continuous alerts within 30 minutes will send only one',
|
||||
specifiedTime: 'Specified Time',
|
||||
deleteTitle: 'Delete Alert',
|
||||
deleteMsg: 'Are you sure you want to delete the alert task?',
|
||||
|
|
@ -3715,6 +3715,19 @@ const message = {
|
|||
portHelper: 'SSL usually uses 465, TLS usually uses 587',
|
||||
sslHelper: 'If the SMTP port is 465, SSL is usually required',
|
||||
tlsHelper: 'If the SMTP port is 587, TLS is usually required',
|
||||
triggerCondition: 'Trigger Condition',
|
||||
loginFail: ' login failures within',
|
||||
nodeException: 'Node Exception Alert',
|
||||
licenseException: 'License Exception Alert',
|
||||
panelLogin: 'Panel Login Exception Alert',
|
||||
sshLogin: 'SSH Login Exception Alert',
|
||||
panelIpLogin: 'Panel Login IP Exception Alert',
|
||||
sshIpLogin: 'SSH Login IP Exception Alert',
|
||||
ipWhiteListHelper: 'IPs in the whitelist are not restricted by any rules',
|
||||
nodeExceptionRule: 'Node exception alert, sent {0} times per day',
|
||||
licenseExceptionRule: 'License exception alert, sent {0} times per day',
|
||||
panelLoginRule: 'Panel login alert, sent {0} times per day',
|
||||
sshLoginRule: 'SSH login alert, sent {0} times per day',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: 'Ling Xia Gold',
|
||||
|
|
|
|||
|
|
@ -3466,7 +3466,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: '指定時間内の平均CPU使用率が指定した値を超過',
|
||||
memoryUseExceedAvgHelper: '指定時間内の平均メモリ使用率が指定した値を超過',
|
||||
loadUseExceedAvgHelper: '指定時間内の平均負荷使用率が指定した値を超過',
|
||||
resourceAlertRulesHelper: '注意:30分以内に連続してアラートが発生した場合、SMSは1回だけ送信されます',
|
||||
resourceAlertRulesHelper: '注意:30分以内に連続してアラートが発生した場合、は1回だけ送信されます',
|
||||
specifiedTime: '指定時間',
|
||||
deleteTitle: 'アラートを削除',
|
||||
deleteMsg: 'アラートタスクを削除してもよろしいですか?',
|
||||
|
|
@ -3594,6 +3594,19 @@ const message = {
|
|||
portHelper: 'SSLは通常465、TLSは通常587',
|
||||
sslHelper: 'SMTPポートが465の場合、通常はSSLが必要です',
|
||||
tlsHelper: 'SMTPポートが587の場合、通常はTLSが必要です',
|
||||
triggerCondition: 'トリガー条件',
|
||||
loginFail: '以内にログイン失敗',
|
||||
nodeException: 'ノード異常アラート',
|
||||
licenseException: 'ライセンス異常アラート',
|
||||
panelLogin: 'パネルログイン異常アラート',
|
||||
sshLogin: 'SSHログイン異常アラート',
|
||||
panelIpLogin: 'パネルログインIP異常アラート',
|
||||
sshIpLogin: 'SSHログインIP異常アラート',
|
||||
ipWhiteListHelper: 'ホワイトリスト内のIPは、いかなるルールの制限も受けません',
|
||||
nodeExceptionRule: 'ノード異常アラートは、1日あたり{0}回送信',
|
||||
licenseExceptionRule: 'ライセンス異常アラートは、1日あたり{0}回送信',
|
||||
panelLoginRule: 'パネルログインアラートは、1日あたり{0}回送信',
|
||||
sshLoginRule: 'SSHログインアラートは、1日あたり{0}回送信',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: '凌霞金',
|
||||
|
|
|
|||
|
|
@ -3404,7 +3404,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: '지정된 시간 내의 평균 CPU 사용량이 지정된 값을 초과함',
|
||||
memoryUseExceedAvgHelper: '지정된 시간 내의 평균 메모리 사용량이 지정된 값을 초과함',
|
||||
loadUseExceedAvgHelper: '지정된 시간 내의 평균 부하 사용량이 지정된 값을 초과함',
|
||||
resourceAlertRulesHelper: '참고: 30분 내에 연속적인 알림은 SMS 한 번만 발송됩니다',
|
||||
resourceAlertRulesHelper: '참고: 30분 내에 연속적인 알림은 한 번만 발송됩니다',
|
||||
specifiedTime: '지정된 시간',
|
||||
deleteTitle: '알림 삭제',
|
||||
deleteMsg: '알림 작업을 삭제하시겠습니까?',
|
||||
|
|
@ -3529,6 +3529,19 @@ const message = {
|
|||
portHelper: 'SSL 은 일반적으로 465, TLS 는 587',
|
||||
sslHelper: 'SMTP 포트가 465 이면 일반적으로 SSL 이 필요합니다',
|
||||
tlsHelper: 'SMTP 포트가 587 이면 일반적으로 TLS 가 필요합니다',
|
||||
triggerCondition: '트리거 조건',
|
||||
loginFail: ' 이내 로그인 실패',
|
||||
nodeException: '노드 이상 알림',
|
||||
licenseException: '라이선스 이상 알림',
|
||||
panelLogin: '패널 로그인 이상 알림',
|
||||
sshLogin: 'SSH 로그인 이상 알림',
|
||||
panelIpLogin: '패널 로그인 IP 이상 알림',
|
||||
sshIpLogin: 'SSH 로그인 IP 이상 알림',
|
||||
ipWhiteListHelper: '화이트리스트에 있는 IP는 어떠한 규칙의 제한도 받지 않습니다',
|
||||
nodeExceptionRule: '노드 이상 알림은 하루 {0}회 전송',
|
||||
licenseExceptionRule: '라이선스 이상 알림은 하루 {0}회 전송',
|
||||
panelLoginRule: '패널 로그인 알림은 하루 {0}회 전송',
|
||||
sshLoginRule: 'SSH 로그인 알림은 하루 {0}회 전송',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: '링샤 골드',
|
||||
|
|
|
|||
|
|
@ -3545,7 +3545,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: 'Penggunaan CPU purata dalam masa tertentu melebihi nilai yang ditetapkan',
|
||||
memoryUseExceedAvgHelper: 'Penggunaan memori purata dalam masa tertentu melebihi nilai yang ditetapkan',
|
||||
loadUseExceedAvgHelper: 'Penggunaan beban purata dalam masa tertentu melebihi nilai yang ditetapkan',
|
||||
resourceAlertRulesHelper: 'Nota: Amaran berterusan dalam masa 30 minit hanya akan menghantar satu SMS',
|
||||
resourceAlertRulesHelper: 'Nota: Amaran berterusan dalam masa 30 minit hanya akan menghantar satu',
|
||||
specifiedTime: 'Masa Tertentu',
|
||||
deleteTitle: 'Padam Amaran',
|
||||
deleteMsg: 'Adakah anda pasti ingin memadam tugas amaran?',
|
||||
|
|
@ -3679,6 +3679,19 @@ const message = {
|
|||
portHelper: 'SSL biasanya 465, TLS biasanya 587',
|
||||
sslHelper: 'Jika port SMTP ialah 465, SSL biasanya diperlukan',
|
||||
tlsHelper: 'Jika port SMTP ialah 587, TLS biasanya diperlukan',
|
||||
triggerCondition: 'Syarat Pencetus',
|
||||
loginFail: ' kegagalan log masuk dalam',
|
||||
nodeException: 'Amaran Kerosakan Nod',
|
||||
licenseException: 'Amaran Kerosakan Lesen',
|
||||
panelLogin: 'Amaran Log Masuk Panel Tidak Normal',
|
||||
sshLogin: 'Amaran Log Masuk SSH Tidak Normal',
|
||||
panelIpLogin: 'Amaran IP Log Masuk Panel Tidak Normal',
|
||||
sshIpLogin: 'Amaran IP Log Masuk SSH Tidak Normal',
|
||||
ipWhiteListHelper: 'IP dalam senarai putih tidak tertakluk kepada sebarang peraturan',
|
||||
nodeExceptionRule: 'Amaran kerosakan nod, dihantar {0} kali sehari',
|
||||
licenseExceptionRule: 'Amaran kerosakan lesen, dihantar {0} kali sehari',
|
||||
panelLoginRule: 'Amaran log masuk panel, dihantar {0} kali sehari',
|
||||
sshLoginRule: 'Amaran log masuk SSH, dihantar {0} kali sehari',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: 'Ling Xia Emas',
|
||||
|
|
|
|||
|
|
@ -3554,7 +3554,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: 'O uso médio da CPU dentro do tempo especificado excede o valor especificado',
|
||||
memoryUseExceedAvgHelper: 'O uso médio da memória dentro do tempo especificado excede o valor especificado',
|
||||
loadUseExceedAvgHelper: 'O uso médio da carga dentro do tempo especificado excede o valor especificado',
|
||||
resourceAlertRulesHelper: 'Nota: Alertas contínuos em 30 minutos enviarão apenas um SMS',
|
||||
resourceAlertRulesHelper: 'Nota: Alertas contínuos em 30 minutos enviarão apenas um',
|
||||
specifiedTime: 'Hora Especificada',
|
||||
deleteTitle: 'Excluir Alerta',
|
||||
deleteMsg: 'Tem certeza de que deseja excluir a tarefa de alerta?',
|
||||
|
|
@ -3687,6 +3687,19 @@ const message = {
|
|||
portHelper: 'SSL geralmente usa 465, TLS geralmente usa 587',
|
||||
sslHelper: 'Se a porta SMTP for 465, normalmente é necessário SSL',
|
||||
tlsHelper: 'Se a porta SMTP for 587, normalmente é necessário TLS',
|
||||
triggerCondition: 'Condição de Disparo',
|
||||
loginFail: ' falhas de login em',
|
||||
nodeException: 'Alerta de Exceção de Nó',
|
||||
licenseException: 'Alerta de Exceção de Licença',
|
||||
panelLogin: 'Alerta de Exceção de Login no Painel',
|
||||
sshLogin: 'Alerta de Exceção de Login SSH',
|
||||
panelIpLogin: 'Alerta de Exceção de IP de Login no Painel',
|
||||
sshIpLogin: 'Alerta de Exceção de IP de Login SSH',
|
||||
ipWhiteListHelper: 'IPs na lista branca não estão sujeitos a nenhuma regra',
|
||||
nodeExceptionRule: 'Alerta de exceção de nó, enviado {0} vezes por dia',
|
||||
licenseExceptionRule: 'Alerta de exceção de licença, enviado {0} vezes por dia',
|
||||
panelLoginRule: 'Alerta de login no painel, enviado {0} vezes por dia',
|
||||
sshLoginRule: 'Alerta de login SSH, enviado {0} vezes por dia',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: 'Ling Xia Gold',
|
||||
|
|
|
|||
|
|
@ -3540,7 +3540,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: 'Среднее использование процессора за указанное время превышает заданное значение',
|
||||
memoryUseExceedAvgHelper: 'Среднее использование памяти за указанное время превышает заданное значение',
|
||||
loadUseExceedAvgHelper: 'Средняя нагрузка за указанное время превышает заданное значение',
|
||||
resourceAlertRulesHelper: 'Примечание: Непрерывные уведомления в течение 30 минут отправят только одно SMS',
|
||||
resourceAlertRulesHelper: 'Примечание: Непрерывные уведомления в течение 30 минут отправят только одно',
|
||||
specifiedTime: 'Указанное Время',
|
||||
deleteTitle: 'Удалить Уведомление',
|
||||
deleteMsg: 'Вы уверены, что хотите удалить задачу уведомления?',
|
||||
|
|
@ -3678,6 +3678,19 @@ const message = {
|
|||
portHelper: 'SSL обычно использует 465, TLS — 587',
|
||||
sslHelper: 'Если порт SMTP — 465, обычно требуется SSL',
|
||||
tlsHelper: 'Если порт SMTP — 587, обычно требуется TLS',
|
||||
triggerCondition: 'Условие срабатывания',
|
||||
loginFail: ' неудачных попыток входа в течение',
|
||||
nodeException: 'Оповещение о сбое узла',
|
||||
licenseException: 'Оповещение о сбое лицензии',
|
||||
panelLogin: 'Оповещение о сбое входа в панель',
|
||||
sshLogin: 'Оповещение о сбое входа по SSH',
|
||||
panelIpLogin: 'Оповещение о сбое IP входа в панель',
|
||||
sshIpLogin: 'Оповещение о сбое IP входа по SSH',
|
||||
ipWhiteListHelper: 'IP-адреса в белом списке не подпадают под действие каких-либо правил',
|
||||
nodeExceptionRule: 'Оповещение о сбое узла, отправляется {0} раз в день',
|
||||
licenseExceptionRule: 'Оповещение о сбое лицензии, отправляется {0} раз в день',
|
||||
panelLoginRule: 'Оповещение о входе в панель, отправляется {0} раз в день',
|
||||
sshLoginRule: 'Оповещение о входе по SSH, отправляется {0} раз в день',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: 'Лин Ся Золотой',
|
||||
|
|
|
|||
|
|
@ -3622,7 +3622,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: 'Belirtilen süre içinde ortalama CPU kullanımı belirtilen değeri aşar',
|
||||
memoryUseExceedAvgHelper: 'Belirtilen süre içinde ortalama bellek kullanımı belirtilen değeri aşar',
|
||||
loadUseExceedAvgHelper: 'Belirtilen süre içinde ortalama yük kullanımı belirtilen değeri aşar',
|
||||
resourceAlertRulesHelper: 'Not: 30 dakika içinde sürekli uyarılar yalnızca bir SMS gönderir',
|
||||
resourceAlertRulesHelper: 'Not: 30 dakika içinde sürekli uyarılar yalnızca bir gönderir',
|
||||
specifiedTime: 'Belirtilen Süre',
|
||||
deleteTitle: 'Uyarıyı Sil',
|
||||
deleteMsg: 'Uyarı görevini silmek istediğinizden emin misiniz?',
|
||||
|
|
@ -3757,6 +3757,19 @@ const message = {
|
|||
portHelper: 'SSL genellikle 465, TLS genellikle 587',
|
||||
sslHelper: 'SMTP portu 465 ise genellikle SSL gerekir',
|
||||
tlsHelper: 'SMTP portu 587 ise genellikle TLS gerekir',
|
||||
triggerCondition: 'Tetikleme Koşulu',
|
||||
loginFail: ' içinde oturum açma başarısızlığı',
|
||||
nodeException: 'Düğüm Hatası Uyarısı',
|
||||
licenseException: 'Lisans Hatası Uyarısı',
|
||||
panelLogin: 'Panel Girişi Hatası Uyarısı',
|
||||
sshLogin: 'SSH Girişi Hatası Uyarısı',
|
||||
panelIpLogin: 'Panel Girişi IP Hatası Uyarısı',
|
||||
sshIpLogin: 'SSH Girişi IP Hatası Uyarısı',
|
||||
ipWhiteListHelper: 'Beyaz listedeki IP’ler herhangi bir kuralla kısıtlanmaz',
|
||||
nodeExceptionRule: 'Düğüm hatası uyarısı, günde {0} kez gönderilir',
|
||||
licenseExceptionRule: 'Lisans hatası uyarısı, günde {0} kez gönderilir',
|
||||
panelLoginRule: 'Panel girişi uyarısı, günde {0} kez gönderilir',
|
||||
sshLoginRule: 'SSH girişi uyarısı, günde {0} kez gönderilir',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: 'Ling Xia Altın',
|
||||
|
|
|
|||
|
|
@ -3337,7 +3337,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: '指定時間內 CPU 平均使用率超過指定值',
|
||||
memoryUseExceedAvgHelper: '指定時間內記憶體平均使用率超過指定值',
|
||||
loadUseExceedAvgHelper: '指定時間內負載平均使用率超過指定值',
|
||||
resourceAlertRulesHelper: '注意:30分鐘內持續告警只發送一次簡訊',
|
||||
resourceAlertRulesHelper: '注意:30分鐘內持續告警只發送一次',
|
||||
specifiedTime: '指定時間',
|
||||
deleteTitle: '删除告警',
|
||||
deleteMsg: '是否確認删除告警任務?',
|
||||
|
|
@ -3460,6 +3460,19 @@ const message = {
|
|||
portHelper: 'SSL 通常為 465,TLS 通常為 587',
|
||||
sslHelper: '若 SMTP 連接埠為 465,通常需要啟用 SSL',
|
||||
tlsHelper: '若 SMTP 連接埠為 587,通常需要啟用 TLS',
|
||||
triggerCondition: '觸發條件',
|
||||
loginFail: '內,登入失敗',
|
||||
nodeException: '節點異常告警',
|
||||
licenseException: '許可證異常告警',
|
||||
panelLogin: '面板登入異常告警',
|
||||
sshLogin: 'SSH 登入異常告警',
|
||||
panelIpLogin: '面板登入 IP 異常告警',
|
||||
sshIpLogin: 'SSH 登入 IP 異常告警',
|
||||
ipWhiteListHelper: '白名單中的 IP 不受任何規則限制',
|
||||
nodeExceptionRule: '節點異常告警,每天發送 {0} 次',
|
||||
licenseExceptionRule: '許可證異常告警,每天發送 {0} 次',
|
||||
panelLoginRule: '面板登入告警,每天發送 {0} 次',
|
||||
sshLoginRule: 'SSH 登入告警,每天發送 {0} 次',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: '凌霞金',
|
||||
|
|
|
|||
|
|
@ -3306,7 +3306,7 @@ const message = {
|
|||
cpuUseExceedAvgHelper: '指定时间内 CPU 平均使用率超过指定值',
|
||||
memoryUseExceedAvgHelper: '指定时间内内存平均使用率超过指定值',
|
||||
loadUseExceedAvgHelper: '指定时间内负载平均使用率超过指定值',
|
||||
resourceAlertRulesHelper: '注意:30 分钟内持续告警只发送一次短信',
|
||||
resourceAlertRulesHelper: '注意:30 分钟内持续告警只发送一次',
|
||||
specifiedTime: '指定时间',
|
||||
deleteTitle: '删除告警',
|
||||
deleteMsg: '是否确认删除告警任务?',
|
||||
|
|
@ -3430,6 +3430,19 @@ const message = {
|
|||
portHelper: 'SSL 通常为465,TLS 通常为587',
|
||||
sslHelper: '如果 SMTP 端口是 465,通常需要启用 SSL',
|
||||
tlsHelper: '如果 SMTP 端口是 587,通常需要启用 TLS',
|
||||
triggerCondition: '触发条件',
|
||||
loginFail: '内,登录失败',
|
||||
nodeException: '节点异常告警',
|
||||
licenseException: '许可证异常告警',
|
||||
panelLogin: '面板登录异常告警',
|
||||
sshLogin: 'SSH 登录异常告警',
|
||||
panelIpLogin: '面板登录 IP 异常告警',
|
||||
sshIpLogin: 'SSH 登录 IP 异常告警',
|
||||
ipWhiteListHelper: '白名单中的 IP 不受任何规则限制',
|
||||
nodeExceptionRule: '节点异常告警,每天发送 {0} 次',
|
||||
licenseExceptionRule: '许可证异常告警,每天发送 {0} 次',
|
||||
panelLoginRule: '面板登录告警,每天发送 {0} 次',
|
||||
sshLoginRule: 'SSH 登录告警告警,每天发送 {0} 次',
|
||||
},
|
||||
theme: {
|
||||
lingXiaGold: '凌霞金',
|
||||
|
|
|
|||
|
|
@ -675,14 +675,12 @@ let fileTypes = {
|
|||
text: ['.iso', '.tiff', '.exe', '.so', '.bz', '.dmg', '.apk', '.pptx', '.ppt', '.xlsb'],
|
||||
};
|
||||
|
||||
export const getFileType = (extension: string) => {
|
||||
let type = 'text';
|
||||
Object.entries(fileTypes).forEach(([key, extensions]) => {
|
||||
if (extensions.includes(extension.toLowerCase())) {
|
||||
type = key;
|
||||
}
|
||||
});
|
||||
return type;
|
||||
export const getFileType = (extension?: string) => {
|
||||
const ext = extension?.toLowerCase();
|
||||
if (!ext) return 'text';
|
||||
|
||||
const match = Object.entries(fileTypes).find((extensions) => extensions.includes(ext));
|
||||
return match ? match[0] : 'text';
|
||||
};
|
||||
|
||||
export const newUUID = () => {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@
|
|||
<template v-if="isMaster">
|
||||
<el-option value="panelPwdEndTime" :label="$t('xpack.alert.panelPwdEndTime')" />
|
||||
<el-option value="panelUpdate" :label="$t('xpack.alert.panelUpdate')" />
|
||||
<el-option value="nodeException" :label="$t('xpack.alert.nodeException')" />
|
||||
<el-option value="licenseException" :label="$t('xpack.alert.licenseException')" />
|
||||
<el-option value="panelLogin" :label="$t('xpack.alert.panelLogin')" />
|
||||
</template>
|
||||
<el-option value="sshLogin" :label="$t('xpack.alert.sshLogin')" />
|
||||
<el-option value="clams" :label="$t('xpack.alert.clams')" />
|
||||
<el-option value="shell" :label="$t('xpack.alert.cronjob') + '-' + $t('cronjob.shell')" />
|
||||
<el-option value="app" :label="$t('xpack.alert.cronjob') + '-' + $t('cronjob.app')" />
|
||||
|
|
@ -238,6 +242,10 @@ const formatRule = (row: Alert.AlertInfo) => {
|
|||
cutWebsiteLog: () => t('xpack.alert.cronJobCutWebsiteLogRule', [row.sendCount]),
|
||||
clean: () => t('xpack.alert.cronJobCleanRule', [row.sendCount]),
|
||||
ntp: () => t('xpack.alert.cronJobNtpRule', [row.sendCount]),
|
||||
nodeException: () => t('xpack.alert.nodeExceptionRule', [row.sendCount]),
|
||||
licenseException: () => t('xpack.alert.licenseExceptionRule', [row.sendCount]),
|
||||
panelLogin: () => t('xpack.alert.panelLoginRule', [row.sendCount]),
|
||||
sshLogin: () => t('xpack.alert.sshLoginRule', [row.sendCount]),
|
||||
};
|
||||
|
||||
return ruleTemplates[row.type] ? ruleTemplates[row.type]() : '';
|
||||
|
|
|
|||
|
|
@ -16,18 +16,14 @@
|
|||
v-model="dialogData.rowData!.type"
|
||||
:disabled="dialogData.title === 'edit'"
|
||||
>
|
||||
<el-option value="ssl" :label="$t('xpack.alert.ssl')" />
|
||||
<el-option value="siteEndTime" :label="$t('xpack.alert.siteEndTime')" />
|
||||
<template v-if="isMaster">
|
||||
<el-option value="panelPwdEndTime" :label="$t('xpack.alert.panelPwdEndTime')" />
|
||||
<el-option value="panelUpdate" :label="$t('xpack.alert.panelUpdate')" />
|
||||
<template v-for="item in allTaskOptions">
|
||||
<el-option
|
||||
:key="item.value"
|
||||
v-if="item.show"
|
||||
:value="item.value"
|
||||
:label="$t(item.label)"
|
||||
/>
|
||||
</template>
|
||||
<el-option value="clams" :label="$t('xpack.alert.clams')" />
|
||||
<el-option value="cronJob" :label="$t('xpack.alert.cronjob')" />
|
||||
<el-option value="cpu" :label="$t('xpack.alert.cpu')" />
|
||||
<el-option value="memory" :label="$t('xpack.alert.memory')" />
|
||||
<el-option value="load" :label="$t('xpack.alert.load')" />
|
||||
<el-option value="disk" :label="$t('xpack.alert.disk')" />
|
||||
</el-select>
|
||||
<span
|
||||
class="input-help"
|
||||
|
|
@ -271,6 +267,43 @@
|
|||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
:label="$t('xpack.alert.triggerCondition')"
|
||||
v-if="ipTypes.includes(dialogData.rowData!.type)"
|
||||
prop="count"
|
||||
>
|
||||
<div class="flex items-center flex-row md:flex-nowrap flex-wrap justify-between gap-x-3 w-full">
|
||||
<el-form-item prop="cycle">
|
||||
<el-input v-model.number="dialogData.rowData!.cycle" :max="200">
|
||||
<template #append>{{ $t('commons.units.minute') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<span class="whitespace-nowrap input-help w-[4.5rem]">
|
||||
{{ $t('xpack.alert.loginFail') }}
|
||||
</span>
|
||||
<el-form-item prop="count">
|
||||
<el-input v-model.number="dialogData.rowData!.count">
|
||||
<template #append>{{ $t('commons.units.time') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
:label="$t('setting.ipWhiteList')"
|
||||
prop="advancedParams"
|
||||
v-if="ipTypes.includes(dialogData.rowData!.type)"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('setting.ipWhiteListEgs')"
|
||||
:rows="4"
|
||||
v-model="dialogData.rowData!.advancedParams"
|
||||
/>
|
||||
<span class="input-help">{{ $t('xpack.alert.ipWhiteListHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('xpack.alert.sendCount')" prop="sendCount">
|
||||
<el-input v-model.number="dialogData.rowData!.sendCount" />
|
||||
<span class="input-help">
|
||||
|
|
@ -297,7 +330,7 @@
|
|||
</el-form-item>
|
||||
<span class="input-help">
|
||||
{{
|
||||
avgTypes.includes(dialogData.rowData!.type) || diskTypes.includes(dialogData.rowData!.type)
|
||||
intervalTypes.includes(dialogData.rowData!.type)
|
||||
? $t('xpack.alert.resourceAlertRulesHelper')
|
||||
: ''
|
||||
}}
|
||||
|
|
@ -333,6 +366,7 @@ import { getSettingInfo } from '@/api/modules/setting';
|
|||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { routerToName } from '@/utils/router';
|
||||
import { checkCidr, checkCidrV6, checkIpV4V6 } from '@/utils/util';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { isMaster } = storeToRefs(globalStore);
|
||||
|
|
@ -357,7 +391,10 @@ type FormInstance = InstanceType<typeof ElForm>;
|
|||
const formRef = ref<FormInstance>();
|
||||
const timeTypes = ['ssl', 'siteEndTime', 'panelPwdEndTime'];
|
||||
const avgTypes = ['cpu', 'memory', 'load'];
|
||||
const ipTypes = ['sshLogin', 'panelLogin'];
|
||||
const noParamTypes = ['panelUpdate'];
|
||||
const intervalTypes = ['cpu', 'memory', 'load', 'disk', 'sshLogin', 'panelLogin', 'nodeException', 'licenseException'];
|
||||
|
||||
const diskTypes = ['disk'];
|
||||
const cronjobTypes = [
|
||||
'shell',
|
||||
|
|
@ -395,8 +432,26 @@ const rules = reactive({
|
|||
count: [Rules.requiredInput, Rules.integerNumber, { validator: checkCount, trigger: 'blur' }],
|
||||
sendCount: [Rules.requiredInput, Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
|
||||
sendMethod: [Rules.requiredSelect],
|
||||
advancedParams: [{ required: false, validator: checkIPs, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const allTaskOptions = [
|
||||
{ value: 'ssl', label: 'xpack.alert.ssl', show: true },
|
||||
{ value: 'siteEndTime', label: 'xpack.alert.siteEndTime', show: true },
|
||||
{ value: 'panelPwdEndTime', label: 'xpack.alert.panelPwdEndTime', show: isMaster.value },
|
||||
{ value: 'panelUpdate', label: 'xpack.alert.panelUpdate', show: isMaster.value },
|
||||
{ value: 'nodeException', label: 'xpack.alert.nodeException', show: isMaster.value },
|
||||
{ value: 'licenseException', label: 'xpack.alert.licenseException', show: isMaster.value },
|
||||
{ value: 'panelLogin', label: 'xpack.alert.panelLogin', show: isMaster.value },
|
||||
{ value: 'sshLogin', label: 'xpack.alert.sshLogin', show: true },
|
||||
{ value: 'clams', label: 'xpack.alert.clams', show: true },
|
||||
{ value: 'cronJob', label: 'xpack.alert.cronjob', show: true },
|
||||
{ value: 'cpu', label: 'xpack.alert.cpu', show: true },
|
||||
{ value: 'memory', label: 'xpack.alert.memory', show: true },
|
||||
{ value: 'load', label: 'xpack.alert.load', show: true },
|
||||
{ value: 'disk', label: 'xpack.alert.disk', show: true },
|
||||
];
|
||||
|
||||
function checkCycle(rule: any, value: any, callback: any) {
|
||||
if (value === '') {
|
||||
callback();
|
||||
|
|
@ -406,6 +461,11 @@ function checkCycle(rule: any, value: any, callback: any) {
|
|||
if (!regex.test(value)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 60])));
|
||||
}
|
||||
} else if (ipTypes.includes(dialogData.value.rowData.type)) {
|
||||
const regex = /^(?:[1-9]|[1-5][0-9]|200)$/;
|
||||
if (!regex.test(value)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 200])));
|
||||
}
|
||||
} else {
|
||||
const regex = /^(?:[1-9]|[12][0-9]|30)$/;
|
||||
if (!regex.test(value)) {
|
||||
|
|
@ -419,7 +479,7 @@ function checkCount(rule: any, value: any, callback: any) {
|
|||
if (value === '') {
|
||||
callback();
|
||||
}
|
||||
if (avgTypes.includes(dialogData.value.rowData.type)) {
|
||||
if (avgTypes.includes(dialogData.value.rowData.type) || ipTypes.includes(dialogData.value.rowData.type)) {
|
||||
const regex = /^(?:[1-9]|[1-9][0-9]|100)$/;
|
||||
if (!regex.test(value)) {
|
||||
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 100])));
|
||||
|
|
@ -464,6 +524,29 @@ function checkSendCount(rule: any, value: any, callback: any) {
|
|||
callback();
|
||||
}
|
||||
|
||||
function checkIPs(rule: any, value: any, callback: any) {
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
let addr = value.split('\n');
|
||||
for (const item of addr) {
|
||||
if (item === '') {
|
||||
continue;
|
||||
}
|
||||
if (item.indexOf('/') !== -1) {
|
||||
if (item.indexOf(':') !== -1) {
|
||||
if (checkCidrV6(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
} else if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
} else if (checkIpV4V6(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const initOptions = (type: string, subType: string) => {
|
||||
if (type === 'ssl') {
|
||||
loadSSLs();
|
||||
|
|
@ -512,6 +595,10 @@ const changeType = () => {
|
|||
cutWebsiteLog: 0,
|
||||
clean: 0,
|
||||
ntp: 0,
|
||||
sshLogin: 30,
|
||||
panelLogin: 30,
|
||||
nodeException: 0,
|
||||
licenseException: 0,
|
||||
};
|
||||
|
||||
const typeToCountMap = {
|
||||
|
|
@ -535,6 +622,10 @@ const changeType = () => {
|
|||
cutWebsiteLog: 0,
|
||||
clean: 0,
|
||||
ntp: 0,
|
||||
sshLogin: 3,
|
||||
panelLogin: 3,
|
||||
nodeException: 0,
|
||||
licenseException: 0,
|
||||
};
|
||||
const typeToProjectMap = {
|
||||
ssl: 'all',
|
||||
|
|
@ -557,6 +648,10 @@ const changeType = () => {
|
|||
cutWebsiteLog: '',
|
||||
clean: '',
|
||||
ntp: '',
|
||||
sshLogin: 'all',
|
||||
panelLogin: 'all',
|
||||
nodeException: 'all',
|
||||
licenseException: 'all',
|
||||
};
|
||||
|
||||
const rowData = dialogData.value.rowData;
|
||||
|
|
@ -656,6 +751,10 @@ const formatTitle = (row: Alert.AlertInfo) => {
|
|||
cutWebsiteLog: () => t('xpack.alert.cronJobCutWebsiteLogTitle', [formatCronJobName(Number(row.project))]),
|
||||
clean: () => t('xpack.alert.cronJobCleanTitle', [formatCronJobName(Number(row.project))]),
|
||||
ntp: () => t('xpack.alert.cronJobNtpTitle', [formatCronJobName(Number(row.project))]),
|
||||
nodeException: () => t('xpack.alert.nodeException'),
|
||||
licenseException: () => t('xpack.alert.licenseException'),
|
||||
panelLogin: () => t('xpack.alert.panelLogin'),
|
||||
sshLogin: () => t('xpack.alert.sshLogin'),
|
||||
};
|
||||
|
||||
return titleTemplates[row.type] ? titleTemplates[row.type]() : '';
|
||||
|
|
@ -709,7 +808,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
|||
emit('search');
|
||||
visible.value = false;
|
||||
} finally {
|
||||
loading.value = false; // ✅ 确保任何路径都能解除禁用
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -98,7 +98,18 @@ const isProductPro = ref(false);
|
|||
const loading = ref(false);
|
||||
const data = ref();
|
||||
const isOffline = ref('Disable');
|
||||
const resourceTypes = ['cpu', 'memory', 'load', 'disk'];
|
||||
const resourceTypes = [
|
||||
'cpu',
|
||||
'memory',
|
||||
'load',
|
||||
'disk',
|
||||
'nodeException',
|
||||
'licenseException',
|
||||
'panelLogin',
|
||||
'sshLogin',
|
||||
'panelIpLogin',
|
||||
'sshIpLogin',
|
||||
];
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'alert-log-page-size',
|
||||
currentPage: 1,
|
||||
|
|
@ -125,7 +136,10 @@ const buttons = [
|
|||
syncAlert(row);
|
||||
},
|
||||
disabled: (row: Alert.AlertLog) => {
|
||||
return row.method != 'sms' && row.status != 'PushSuccess' && row.status != 'SyncError';
|
||||
return (
|
||||
(row.method != 'sms' && row.status != 'PushSuccess' && row.status != 'SyncError') ||
|
||||
row.status == 'Success'
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -188,6 +202,12 @@ const formatMessage = (row: Alert.AlertInfo) => {
|
|||
cutWebsiteLog: () => t('xpack.alert.cronJobCutWebsiteLogTitle', [row.project]),
|
||||
clean: () => t('xpack.alert.cronJobCleanTitle', [row.project]),
|
||||
ntp: () => t('xpack.alert.cronJobNtpTitle', [row.project]),
|
||||
nodeException: () => t('xpack.alert.nodeException'),
|
||||
licenseException: () => t('xpack.alert.licenseException'),
|
||||
panelLogin: () => t('xpack.alert.panelLogin'),
|
||||
sshLogin: () => t('xpack.alert.sshLogin'),
|
||||
panelIpLogin: () => t('xpack.alert.panelIpLogin'),
|
||||
sshIpLogin: () => t('xpack.alert.sshIpLogin'),
|
||||
};
|
||||
let type = row.type === 'cronJob' ? row.subType : row.type;
|
||||
return messageTemplates[type] ? messageTemplates[type]() : '';
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
v-model="noticeTimeRange"
|
||||
class="!w-[235px] mx-1 mt-1"
|
||||
is-range
|
||||
:start-placeholder="$t('xpack.commons.search.timeStart')"
|
||||
:end-placeholder="$t('xpack.commons.search.结束时间')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
/>
|
||||
<span class="input-help ml-2">
|
||||
{{
|
||||
|
|
@ -37,8 +37,8 @@
|
|||
v-model="resourceTimeRange"
|
||||
class="!w-[235px] mx-1 mt-1"
|
||||
is-range
|
||||
:start-placeholder="$t('xpack.commons.search.timeStart')"
|
||||
:end-placeholder="$t('xpack.commons.search.结束时间')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
/>
|
||||
<span class="input-help ml-2">
|
||||
{{
|
||||
|
|
@ -123,7 +123,18 @@ const config = ref<Alert.AlertConfigInfo>({
|
|||
status: '',
|
||||
config: '',
|
||||
});
|
||||
const resourceValue = ref(['clams', 'cronJob', 'cpu', 'memory', 'load', 'disk']);
|
||||
const resourceValue = ref([
|
||||
'clams',
|
||||
'cronJob',
|
||||
'cpu',
|
||||
'memory',
|
||||
'load',
|
||||
'disk',
|
||||
'nodeException',
|
||||
'licenseException',
|
||||
'panelLogin',
|
||||
'sshLogin',
|
||||
]);
|
||||
const noticeDefaultTime: [Date, Date] = [new Date(0, 0, 1, 8, 0, 0), new Date(0, 0, 1, 23, 59, 59)];
|
||||
const resourceDefaultTime: [Date, Date] = [new Date(0, 0, 1, 0, 0, 0), new Date(0, 0, 1, 23, 59, 59)];
|
||||
const noticeTimeRange = ref(noticeDefaultTime);
|
||||
|
|
@ -140,6 +151,10 @@ const generateData = (): Option[] => {
|
|||
data.push({ key: 'memory', label: i18n.global.t('xpack.alert.memory'), disabled: false });
|
||||
data.push({ key: 'load', label: i18n.global.t('xpack.alert.load'), disabled: false });
|
||||
data.push({ key: 'disk', label: i18n.global.t('xpack.alert.disk'), disabled: false });
|
||||
data.push({ key: 'nodeException', label: i18n.global.t('xpack.alert.nodeException'), disabled: false });
|
||||
data.push({ key: 'licenseException', label: i18n.global.t('xpack.alert.licenseException'), disabled: false });
|
||||
data.push({ key: 'panelLogin', label: i18n.global.t('xpack.alert.panelLogin'), disabled: false });
|
||||
data.push({ key: 'sshLogin', label: i18n.global.t('xpack.alert.sshLogin'), disabled: false });
|
||||
return data;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue