Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/app/api/v2/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
60 changes: 60 additions & 0 deletions agent/app/service/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"mime"
"sort"
"strconv"
"strings"
"sync"
"time"
Expand All @@ -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)
Expand Down Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down
63 changes: 60 additions & 3 deletions agent/app/service/alert_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"math"
"net"
"sort"
"strconv"
"strings"
Expand All @@ -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 (
Expand All @@ -35,7 +36,7 @@ const (

type AlertTaskHelper struct {
DiskIO chan []disk.IOCountersStat
NetIO chan []net.IOCountersStat
NetIO chan []gnet.IOCountersStat
}

type IAlertTaskHelper interface {
Expand All @@ -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() {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/es-ES.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ErrStructTransform: '型変換エラー: {{ .err }}'
ErrNotLogin: 'ログインしていません: {{ .detail }}'
ErrPasswordExpired: 'パスワード期限切れ: {{ .detail }}'
ErrNotSupportType: '未対応のタイプ: {{ .name }}'
ErrAlertMethodNotSupported: 'このバージョンではこの通知方法は利用できません: {{ .name }}'
ErrProxy: 'リクエストに失敗しました: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API利用不可: {{ .detail }}'
ErrApiConfigKeyInvalid: 'APIキーが無効です: {{ .detail }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/ko.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ErrStructTransform: '형 변환 실패: {{ .err }}'
ErrNotLogin: '로그인하지 않음: {{ .detail }}'
ErrPasswordExpired: '비밀번호 만료: {{ .detail }}'
ErrNotSupportType: '지원하지 않는 타입: {{ .name }}'
ErrAlertMethodNotSupported: '현재 버전에서는 이 알림 방식을 지원하지 않습니다: {{ .name }}'
ErrProxy: '요청 실패: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API 사용 중지: {{ .detail }}'
ErrApiConfigKeyInvalid: 'API 키가 잘못됨: {{ .detail }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/ms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/pt-BR.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ErrStructTransform: 'Ошибка преобразования: {{ .err }}'
ErrNotLogin: 'Не авторизован: {{ .detail }}'
ErrPasswordExpired: 'Пароль истёк: {{ .detail }}'
ErrNotSupportType: 'Тип не поддерживается: {{ .name }}'
ErrAlertMethodNotSupported: 'Эта версия не поддерживает данный способ оповещения: {{ .name }}'
ErrProxy: 'Запрос не удался: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API отключено: {{ .detail }}'
ErrApiConfigKeyInvalid: 'Неверный API-ключ: {{ .detail }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/tr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/zh-Hant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ErrStructTransform: '型別轉換失敗: {{ .err }}'
ErrNotLogin: '使用者未登入: {{ .detail }}'
ErrPasswordExpired: '目前密碼已過期: {{ .detail }}'
ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}'
ErrAlertMethodNotSupported: '當前版本不支援此告警方式: {{ .name }}'
ErrProxy: '請求錯誤,請檢查該節點狀態: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API 介面禁止存取: {{ .detail }}'
ErrApiConfigKeyInvalid: 'API 介面金鑰錯誤: {{ .detail }}'
Expand Down
1 change: 1 addition & 0 deletions agent/i18n/lang/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ErrStructTransform: "类型转换失败: {{ .err }}"
ErrNotLogin: "用户未登录: {{ .detail }}"
ErrPasswordExpired: "当前密码已过期: {{ .detail }}"
ErrNotSupportType: "不支持当前类型: {{ .name }}"
ErrAlertMethodNotSupported: "当前版本不支持此告警方式: {{ .name }}"
ErrProxy: "请求失败,请检查节点状态: {{ .detail }}"
ErrApiConfigStatusInvalid: "API 禁止访问: {{ .detail }}"
ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/modules/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading