diff --git a/agent/app/api/v2/alert.go b/agent/app/api/v2/alert.go index 5faa9d4ae273..30aee5f08106 100644 --- a/agent/app/api/v2/alert.go +++ b/agent/app/api/v2/alert.go @@ -287,7 +287,7 @@ func (b *BaseApi) PageAlertConfig(c *gin.Context) { // @Security ApiKeyAuth // @Security Timestamp // @Router /alert/config/update [post] -// @x-panel-log {"bodyKeys":["title"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置 [title]","formatEN":"update alert config [title]"} +// @x-panel-log {"bodyKeys":["id","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置 [id][type]","formatEN":"update alert config [id][type]"} func (b *BaseApi) UpdateAlertConfig(c *gin.Context) { var req dto.AlertConfigUpdate if err := helper.CheckBindAndValidate(&req, c); err != nil { diff --git a/agent/app/service/alert.go b/agent/app/service/alert.go index 18c3de59c518..30d2104515c7 100644 --- a/agent/app/service/alert.go +++ b/agent/app/service/alert.go @@ -5,6 +5,7 @@ import ( "fmt" "mime" "sort" + "strconv" "strings" "sync" "time" @@ -26,6 +27,12 @@ import ( type AlertService struct{} var eeHiddenAlertTypes = []string{"licenseException", "panelUpdate", "panelPwdEndTime"} +var communityAlertMethodTypeNames = map[string]string{ + constant.WeCom: "WeCom", + constant.DingTalk: "DingTalk", + constant.FeiShu: "FeiShu", + constant.SMS: "SMS", +} type IAlertService interface { PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error) @@ -132,6 +139,9 @@ func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) { } func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error { + if err := a.validateCommunityAlertMethod(create.Method); err != nil { + return err + } var alertID uint var alertInfo model.Alert if create.Project != "" { @@ -170,6 +180,9 @@ func (a AlertService) CreateAlert(create dto.AlertCreate, operator string) error } func (a AlertService) UpdateAlert(req dto.AlertUpdate, operator string) error { + if err := a.validateCommunityAlertMethod(req.Method); err != nil { + return err + } upMap := make(map[string]interface{}) upMap["id"] = req.ID @@ -493,6 +506,9 @@ func (a AlertService) PageAlertConfig(req dto.AlertConfigPageReq) (int64, []mode } func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error { + if err := a.validateCommunityAlertConfigType(req.Type); err != nil { + return err + } if err := a.checkAlertConfigDisplayNameUnique(req); err != nil { return err } @@ -545,6 +561,47 @@ func (a AlertService) checkAlertConfigDisplayNameUnique(req dto.AlertConfigUpdat return nil } +func (a AlertService) validateCommunityAlertMethod(method string) error { + if global.CONF.Base.IsEnterprise { + return nil + } + if strings.TrimSpace(method) == "" { + return nil + } + + for _, item := range strings.Split(method, ",") { + item = strings.TrimSpace(item) + if item == "" { + continue + } + if configID, err := strconv.ParseUint(item, 10, 64); err == nil { + config, err := alertRepo.GetConfigById(uint(configID)) + if err != nil { + return err + } + if name, ok := communityAlertMethodTypeNames[config.Type]; ok { + return buserr.WithMap("ErrAlertMethodNotSupported", map[string]interface{}{"name": name}, nil) + } + continue + } + if name, ok := communityAlertMethodTypeNames[item]; ok { + return buserr.WithMap("ErrAlertMethodNotSupported", map[string]interface{}{"name": name}, nil) + } + } + + return nil +} + +func (a AlertService) validateCommunityAlertConfigType(configType string) error { + if global.CONF.Base.IsEnterprise { + return nil + } + if name, ok := communityAlertMethodTypeNames[configType]; ok { + return buserr.WithMap("ErrAlertMethodNotSupported", map[string]interface{}{"name": name}, nil) + } + return nil +} + func alertConfigDisplayName(configType, configData string) string { switch configType { case constant.Email, constant.WeCom, constant.DingTalk, constant.FeiShu, constant.Bark: @@ -605,6 +662,9 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) { } func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate, operator string) error { + if err := a.validateCommunityAlertMethod(updateAlert.Method); err != nil { + return err + } upMap := make(map[string]interface{}) var newStatus string if updateAlert.SendCount == 0 { diff --git a/agent/app/service/alert_helper.go b/agent/app/service/alert_helper.go index 25a445e030c4..0b200594ae57 100644 --- a/agent/app/service/alert_helper.go +++ b/agent/app/service/alert_helper.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math" + "net" "sort" "strconv" "strings" @@ -24,7 +25,7 @@ import ( "github.com/shirou/gopsutil/v4/disk" "github.com/shirou/gopsutil/v4/load" "github.com/shirou/gopsutil/v4/mem" - "github.com/shirou/gopsutil/v4/net" + gnet "github.com/shirou/gopsutil/v4/net" ) const ( @@ -35,7 +36,7 @@ const ( type AlertTaskHelper struct { DiskIO chan []disk.IOCountersStat - NetIO chan []net.IOCountersStat + NetIO chan []gnet.IOCountersStat } type IAlertTaskHelper interface { @@ -55,7 +56,7 @@ var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, " func NewIAlertTaskHelper() IAlertTaskHelper { return &AlertTaskHelper{ DiskIO: make(chan []disk.IOCountersStat, 1), - NetIO: make(chan []net.IOCountersStat, 1), + NetIO: make(chan []gnet.IOCountersStat, 1), } } func (m *AlertTaskHelper) StartTask() { @@ -485,6 +486,7 @@ func loadPanelLogin(alert dto.AlertDTO) { if err != nil { global.LOG.Errorf("Failed to check recent failed ip login logs: %v", err) } + records = filterLoginLogsNotInWhitelist(records, whitelist) if len(records) > 0 { quota := strings.Join(func() []string { var ips []string @@ -534,6 +536,7 @@ func loadSSHLogin(alert dto.AlertDTO) { if err != nil { global.LOG.Errorf("Failed to check recent failed ip ssh login logs: %v", err) } + records = filterSSHLoginEntriesNotInWhitelist(records, whitelist) if len(records) > 0 { quota := strings.Join(records, "\n") params := []dto.Param{ @@ -552,6 +555,60 @@ func loadSSHLogin(alert dto.AlertDTO) { } } +func filterLoginLogsNotInWhitelist(records []model.LoginLog, whitelist []string) []model.LoginLog { + filtered := make([]model.LoginLog, 0, len(records)) + for _, record := range records { + if !isIPInWhitelist(record.IP, whitelist) { + filtered = append(filtered, record) + } + } + return filtered +} + +func filterSSHLoginEntriesNotInWhitelist(records []string, whitelist []string) []string { + filtered := make([]string, 0, len(records)) + for _, record := range records { + ip := record + if idx := strings.Index(record, "-"); idx >= 0 { + ip = record[:idx] + } + if !isIPInWhitelist(ip, whitelist) { + filtered = append(filtered, record) + } + } + return filtered +} + +func isIPInWhitelist(ip string, whitelist []string) bool { + targetIP := net.ParseIP(strings.TrimSpace(ip)) + if targetIP == nil { + return false + } + for _, item := range whitelist { + item = strings.TrimSpace(item) + if item == "" { + continue + } + if item == ip { + return true + } + if whiteIP := net.ParseIP(item); whiteIP != nil { + if whiteIP.Equal(targetIP) { + return true + } + continue + } + _, ipNet, err := net.ParseCIDR(item) + if err != nil { + continue + } + if ipNet.Contains(targetIP) { + return true + } + } + return false +} + func loadNodeException(alert dto.AlertDTO) { // only master alert failCount, err := xpack.AlertProvider.GetNodeErrorAlert() diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 8e054b78565e..f3ebed759385 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -14,6 +14,7 @@ ErrStructTransform: 'Type conversion failed: {{ .err }}' ErrNotLogin: 'Not logged in: {{ .detail }}' ErrPasswordExpired: 'Password expired: {{ .detail }}' ErrNotSupportType: 'Unsupported type: {{ .name }}' +ErrAlertMethodNotSupported: 'This version does not support this alert method: {{ .name }}' ErrProxy: 'Request failed: {{ .detail }}' ErrApiConfigStatusInvalid: 'API access disabled: {{ .detail }}' ErrApiConfigKeyInvalid: 'Invalid API key: {{ .detail }}' diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index c55e322a17c3..ce639c7f1a95 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -14,6 +14,7 @@ ErrStructTransform: 'Error de conversión: {{ .err }}' ErrNotLogin: 'No has iniciado sesión: {{ .detail }}' ErrPasswordExpired: 'Contraseña expirada: {{ .detail }}' ErrNotSupportType: 'Tipo no soportado: {{ .name }}' +ErrAlertMethodNotSupported: 'Esta versión no admite este método de alerta: {{ .name }}' ErrProxy: 'Solicitud fallida: {{ .detail }}' ErrApiConfigStatusInvalid: 'API desactivada: {{ .detail }}' ErrApiConfigKeyInvalid: 'Clave API inválida: {{ .detail }}' diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 3f7df7352079..3942e542e756 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -14,6 +14,7 @@ ErrStructTransform: '型変換エラー: {{ .err }}' ErrNotLogin: 'ログインしていません: {{ .detail }}' ErrPasswordExpired: 'パスワード期限切れ: {{ .detail }}' ErrNotSupportType: '未対応のタイプ: {{ .name }}' +ErrAlertMethodNotSupported: 'このバージョンではこの通知方法は利用できません: {{ .name }}' ErrProxy: 'リクエストに失敗しました: {{ .detail }}' ErrApiConfigStatusInvalid: 'API利用不可: {{ .detail }}' ErrApiConfigKeyInvalid: 'APIキーが無効です: {{ .detail }}' diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index 7f574f751cbc..c5f4c078e121 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -14,6 +14,7 @@ ErrStructTransform: '형 변환 실패: {{ .err }}' ErrNotLogin: '로그인하지 않음: {{ .detail }}' ErrPasswordExpired: '비밀번호 만료: {{ .detail }}' ErrNotSupportType: '지원하지 않는 타입: {{ .name }}' +ErrAlertMethodNotSupported: '현재 버전에서는 이 알림 방식을 지원하지 않습니다: {{ .name }}' ErrProxy: '요청 실패: {{ .detail }}' ErrApiConfigStatusInvalid: 'API 사용 중지: {{ .detail }}' ErrApiConfigKeyInvalid: 'API 키가 잘못됨: {{ .detail }}' diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index 582e62f62026..fe18e94b739c 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -14,6 +14,7 @@ ErrStructTransform: 'Ralat penukaran: {{ .err }}' ErrNotLogin: 'Belum log masuk: {{ .detail }}' ErrPasswordExpired: 'Kata laluan tamat: {{ .detail }}' ErrNotSupportType: 'Jenis tidak disokong: {{ .name }}' +ErrAlertMethodNotSupported: 'Versi ini tidak menyokong kaedah amaran ini: {{ .name }}' ErrProxy: 'Permintaan gagal: {{ .detail }}' ErrApiConfigStatusInvalid: 'API dilumpuhkan: {{ .detail }}' ErrApiConfigKeyInvalid: 'Kunci API tidak sah: {{ .detail }}' diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 309f538baf03..4664614d37a8 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -14,6 +14,7 @@ ErrStructTransform: 'Erro de conversão: {{ .err }}' ErrNotLogin: 'Sem sessão: {{ .detail }}' ErrPasswordExpired: 'Senha expirada: {{ .detail }}' ErrNotSupportType: 'Tipo não suportado: {{ .name }}' +ErrAlertMethodNotSupported: 'Esta versão não oferece suporte a este método de alerta: {{ .name }}' ErrProxy: 'Requisição falhou: {{ .detail }}' ErrApiConfigStatusInvalid: 'API desativada: {{ .detail }}' ErrApiConfigKeyInvalid: 'Chave API inválida: {{ .detail }}' diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index f7c75eb6ee52..3d940175cfe8 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -14,6 +14,7 @@ ErrStructTransform: 'Ошибка преобразования: {{ .err }}' ErrNotLogin: 'Не авторизован: {{ .detail }}' ErrPasswordExpired: 'Пароль истёк: {{ .detail }}' ErrNotSupportType: 'Тип не поддерживается: {{ .name }}' +ErrAlertMethodNotSupported: 'Эта версия не поддерживает данный способ оповещения: {{ .name }}' ErrProxy: 'Запрос не удался: {{ .detail }}' ErrApiConfigStatusInvalid: 'API отключено: {{ .detail }}' ErrApiConfigKeyInvalid: 'Неверный API-ключ: {{ .detail }}' diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 289729b412e7..354f46a4319d 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -14,6 +14,7 @@ ErrStructTransform: 'Dönüşüm hatası: {{ .err }}' ErrNotLogin: 'Oturum açılmamış: {{ .detail }}' ErrPasswordExpired: 'Şifre süresi doldu: {{ .detail }}' ErrNotSupportType: 'Bu tür desteklenmiyor: {{ .name }}' +ErrAlertMethodNotSupported: 'Bu sürüm bu uyarı yöntemini desteklemiyor: {{ .name }}' ErrProxy: 'İstek başarısız: {{ .detail }}' ErrApiConfigStatusInvalid: 'API kapalı: {{ .detail }}' ErrApiConfigKeyInvalid: 'Geçersiz API anahtarı: {{ .detail }}' diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index a656e4b607e9..5573735e62a6 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -14,6 +14,7 @@ ErrStructTransform: '型別轉換失敗: {{ .err }}' ErrNotLogin: '使用者未登入: {{ .detail }}' ErrPasswordExpired: '目前密碼已過期: {{ .detail }}' ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}' +ErrAlertMethodNotSupported: '當前版本不支援此告警方式: {{ .name }}' ErrProxy: '請求錯誤,請檢查該節點狀態: {{ .detail }}' ErrApiConfigStatusInvalid: 'API 介面禁止存取: {{ .detail }}' ErrApiConfigKeyInvalid: 'API 介面金鑰錯誤: {{ .detail }}' diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 4b656d35e3e2..48438b3d4250 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -14,6 +14,7 @@ ErrStructTransform: "类型转换失败: {{ .err }}" ErrNotLogin: "用户未登录: {{ .detail }}" ErrPasswordExpired: "当前密码已过期: {{ .detail }}" ErrNotSupportType: "不支持当前类型: {{ .name }}" +ErrAlertMethodNotSupported: "当前版本不支持此告警方式: {{ .name }}" ErrProxy: "请求失败,请检查节点状态: {{ .detail }}" ErrApiConfigStatusInvalid: "API 禁止访问: {{ .detail }}" ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}" diff --git a/frontend/src/api/modules/alert.ts b/frontend/src/api/modules/alert.ts index cd9c7d0c08c0..181187f53a07 100644 --- a/frontend/src/api/modules/alert.ts +++ b/frontend/src/api/modules/alert.ts @@ -9,7 +9,7 @@ const alertConfigHiddenTypes = ['sms']; const resolveAlertConfigExcludeTypes = (excludeTypes: string[] = []) => { const globalStore = GlobalStore(); const types = new Set(excludeTypes); - if (!(globalStore.isProductPro && !globalStore.isIntl && !globalStore.isEE)) { + if (globalStore.isIntl || globalStore.isEE) { alertConfigHiddenTypes.forEach((type) => types.add(type)); } return Array.from(types); diff --git a/frontend/src/views/setting/alert/dash/task/index.vue b/frontend/src/views/setting/alert/dash/task/index.vue index d5382c9b298b..f319e8440d97 100644 --- a/frontend/src/views/setting/alert/dash/task/index.vue +++ b/frontend/src/views/setting/alert/dash/task/index.vue @@ -342,7 +342,7 @@ :key="opt.value" :value="opt.value" :label="opt.label" - :disabled="opt.disabled" + :disabled="isLockedMethodOption(opt)" >