From a9a329b8abedf18c2b1ed4bf117d65c7322860b7 Mon Sep 17 00:00:00 2001 From: Magnus Root Date: Thu, 4 Jun 2026 12:06:59 +0300 Subject: [PATCH] Fix alerts and change make names for dashs --- .gitignore | 0 ARCHITECTURE.md | 0 DATABASE_SCHEMA.md | 34 +++++++++++++++++++++++++++++++--- LICENSE | 0 README.md | 0 alerts.go | 4 ++-- alerts_vl.go | 4 ++-- config.go | 0 dashboard.go | 0 dashboard_vl.go | 0 database.go | 33 +++++++++++++++++++++++++-------- env.go | 0 git.go | 0 go.mod | 0 go.sum | 0 grafana.go | 0 main.go | 0 panels.go | 0 panels_vl.go | 0 state.go | 0 template.go | 0 template_validator.go | 0 22 files changed, 60 insertions(+), 15 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 ARCHITECTURE.md mode change 100644 => 100755 DATABASE_SCHEMA.md mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 alerts.go mode change 100644 => 100755 alerts_vl.go mode change 100644 => 100755 config.go mode change 100644 => 100755 dashboard.go mode change 100644 => 100755 dashboard_vl.go mode change 100644 => 100755 database.go mode change 100644 => 100755 env.go mode change 100644 => 100755 git.go mode change 100644 => 100755 go.mod mode change 100644 => 100755 go.sum mode change 100644 => 100755 grafana.go mode change 100644 => 100755 main.go mode change 100644 => 100755 panels.go mode change 100644 => 100755 panels_vl.go mode change 100644 => 100755 state.go mode change 100644 => 100755 template.go mode change 100644 => 100755 template_validator.go diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md old mode 100644 new mode 100755 diff --git a/DATABASE_SCHEMA.md b/DATABASE_SCHEMA.md old mode 100644 new mode 100755 index 56191cb..7f0639f --- a/DATABASE_SCHEMA.md +++ b/DATABASE_SCHEMA.md @@ -52,8 +52,8 @@ SELECT sid, domain_name, aliases FROM sp_info WHERE sid IS NOT NULL LIMIT 3; |---|---|---|---| | `l7resourceid` | `text` | — | SID ресурса (FK к `sp_info.sid` / `manual_info.sid`) | | `client_title` | `text` | — | Название клиента — имя тенанта в дашборде | -| `four_hundred` | `integer` | 20 | Порог 4xx ошибок в % для данного SID | -| `five_hundred` | `integer` | 10 | Порог 5xx ошибок в % для данного SID | +| `four_hundred` | `integer` | 20 | Порог 4xx ошибок в % для данного SID. `100` = алерт не создавать | +| `five_hundred` | `integer` | 10 | Порог 5xx ошибок в % для данного SID. `100` = алерт не создавать | | `ptaf_fallback_code_alert_count` | `integer` | 1 | Порог WAF block алерта в штуках за минуту | **Пример:** @@ -85,6 +85,7 @@ FROM apps_settings LIMIT 5; | `rps_commercial_limit` | `integer` | NULL | Коммерческий лимит RPS — синяя линия на графике. NULL → 100 | | `waf_provider` | `text` | NULL | Тип провайдера WAF. NULL = клиент игнорируется | | `ptaf_fallback_code` | `text` | NULL | HTTP код блокировки PTAF (`418`, `403` и т.д.). `pass` или NULL = WAF block алерт не создавать | +| `parent_client_title` | `text` | NULL | Родительский клиент для группировки. Если задан — клиент объединяется с другими клиентами с тем же `parent_client_title` в одну секцию дашборда | **Значения `waf_provider`:** @@ -205,6 +206,31 @@ UPDATE client_info SET waf_provider = NULL WHERE client_title = 'Названи > После смены `waf_provider` при следующем запуске генератора старые алерты будут удалены автоматически и созданы новые в нужной группе. +### Группировка клиентов + +```sql +-- Объединить двух клиентов в одну секцию дашборда +UPDATE client_info SET parent_client_title = 'BigGroup' +WHERE client_title IN ('client_a', 'client_b'); + +-- Убрать группировку +UPDATE client_info SET parent_client_title = NULL +WHERE client_title = 'client_a'; +``` + +> Клиенты с одинаковым `parent_client_title` объединяются в одну строку дашборда. +> Все их домены (SID) попадают в одну секцию. `RPSLimit` берётся максимальный по группе. + +### Отключение алертов по ошибкам для конкретного SID + +```sql +-- Отключить алерты по 4xx для конкретного домена (значение 100 = не создавать алерт) +UPDATE apps_settings SET four_hundred = 100 WHERE l7resourceid = 'SID12345'; + +-- Отключить алерты по 5xx для конкретного домена +UPDATE apps_settings SET five_hundred = 100 WHERE l7resourceid = 'SID12345'; +``` + ### Настройка WAF block алерта ```sql @@ -283,6 +309,8 @@ ORDER BY a.client_title; | Новый SID в `sp_info`/`manual_info` + запись в `apps_settings` | Новый домен добавится в строку клиента | | Новый `client_title` + `waf_provider` в `client_info` | Новая строка клиента в соответствующем дашборде | | Изменение `waf_provider` (`spik` → `sp` или наоборот) | Клиент переносится в другой дашборд, старые алерты удаляются, создаются новые | +| Установка `parent_client_title` | Клиент объединяется с другими клиентами группы в одну секцию дашборда | +| `four_hundred = 100` или `five_hundred = 100` | Алерты по соответствующему коду для данного SID не создаются | | Установка `waf_provider = NULL` | Клиент исчезает из всех дашбордов, алерты удаляются | | Изменение `rps_limit` | Пересоздание алерта RPS, обновление красной линии на графике | | Изменение `rps_commercial_limit` | Обновление синей линии на графике | @@ -294,4 +322,4 @@ ORDER BY a.client_title; | Удаление из `apps_settings` | Клиент/домен исчезает из дашборда, алерты удаляются автоматически | > Изменения в БД обнаруживаются через хэш (`alerts_hash` в `state.json`). При изменении любого поля — алерты пересоздаются автоматически при следующем запуске. -> Устаревшие алерты удаляются автоматически через `deleteObsoleteAlerts()`. +> Устаревшие алерты удаляются автоматически через `deleteObsoleteAlerts()`. \ No newline at end of file diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/alerts.go b/alerts.go old mode 100644 new mode 100755 index 32c99bc..234fdd4 --- a/alerts.go +++ b/alerts.go @@ -357,7 +357,7 @@ func generateAndSendAlerts(clients map[string]ClientData, tmpl AlertRulesTemplat // Генерируем для response_status_code и upstream_status_code for _, domain := range t.client.Domains { for _, statusField := range []string{"response_status_code", "upstream_status"} { - if domain.Limit4xx > 0 { + if domain.Limit4xx > 0 && domain.Limit4xx < 100 { for _, rule := range buildErrorRateAlertRules(t.client, domain, 4, domain.Limit4xx, statusField, tmpl, config) { rule["folderUID"] = folderUID tasks = append(tasks, alertTask{ @@ -366,7 +366,7 @@ func generateAndSendAlerts(clients map[string]ClientData, tmpl AlertRulesTemplat }) } } - if domain.Limit5xx > 0 { + if domain.Limit5xx > 0 && domain.Limit5xx < 100 { for _, rule := range buildErrorRateAlertRules(t.client, domain, 5, domain.Limit5xx, statusField, tmpl, config) { rule["folderUID"] = folderUID tasks = append(tasks, alertTask{ diff --git a/alerts_vl.go b/alerts_vl.go old mode 100644 new mode 100755 index 3dff1e1..12901bf --- a/alerts_vl.go +++ b/alerts_vl.go @@ -405,7 +405,7 @@ func generateAndSendAlertsVL(clients map[string]ClientData, tmpl AlertRulesTempl // Генерируем для response_status_code и upstream_status_code for _, domain := range client.Domains { for _, statusField := range []string{"response_status_code", "upstream_status"} { - if domain.Limit4xx > 0 { + if domain.Limit4xx > 0 && domain.Limit4xx < 100 { for _, rule := range buildErrorRateAlertRulesVL(client, domain, 4, domain.Limit4xx, statusField, tmpl, config) { rule["folderUID"] = folderUID tasks = append(tasks, alertTask{ @@ -414,7 +414,7 @@ func generateAndSendAlertsVL(clients map[string]ClientData, tmpl AlertRulesTempl }) } } - if domain.Limit5xx > 0 { + if domain.Limit5xx > 0 && domain.Limit5xx < 100 { for _, rule := range buildErrorRateAlertRulesVL(client, domain, 5, domain.Limit5xx, statusField, tmpl, config) { rule["folderUID"] = folderUID tasks = append(tasks, alertTask{ diff --git a/config.go b/config.go old mode 100644 new mode 100755 diff --git a/dashboard.go b/dashboard.go old mode 100644 new mode 100755 diff --git a/dashboard_vl.go b/dashboard_vl.go old mode 100644 new mode 100755 diff --git a/database.go b/database.go old mode 100644 new mode 100755 index 01e26a1..da6aa94 --- a/database.go +++ b/database.go @@ -42,7 +42,8 @@ func fetchClientsData(db *sql.DB) (map[string]ClientData, error) { COALESCE(a.five_hundred, 10) as limit_5xx, COALESCE(a.ptaf_fallback_code_alert_count, 1) as ptaf_fallback_code_alert_count, COALESCE(MAX(ci.waf_provider) OVER (PARTITION BY a.client_title), '') as waf_provider, - COALESCE(MAX(ci.ptaf_fallback_code) OVER (PARTITION BY a.client_title), '') as ptaf_fallback_code + COALESCE(MAX(ci.ptaf_fallback_code) OVER (PARTITION BY a.client_title), '') as ptaf_fallback_code, + COALESCE(MAX(ci.parent_client_title) OVER (PARTITION BY a.client_title), '') as parent_client_title FROM ( SELECT sid, domain_name, aliases FROM sp_info WHERE sid IS NOT NULL UNION @@ -63,28 +64,44 @@ func fetchClientsData(db *sql.DB) (map[string]ClientData, error) { clients := make(map[string]ClientData) for rows.Next() { - var clientTitle, sid, domainName, aliasesJSON, wafProvider, ptafFallbackCode string + var clientTitle, sid, domainName, aliasesJSON, wafProvider, ptafFallbackCode, parentClientTitle string var rpsLimit, rpsCommercialLimit, limit4xx, limit5xx, ptafFallbackCodeAlertCount int - if err := rows.Scan(&clientTitle, &sid, &domainName, &aliasesJSON, &rpsLimit, &rpsCommercialLimit, &limit4xx, &limit5xx, &ptafFallbackCodeAlertCount, &wafProvider, &ptafFallbackCode); err != nil { + if err := rows.Scan(&clientTitle, &sid, &domainName, &aliasesJSON, &rpsLimit, &rpsCommercialLimit, &limit4xx, &limit5xx, &ptafFallbackCodeAlertCount, &wafProvider, &ptafFallbackCode, &parentClientTitle); err != nil { return nil, fmt.Errorf("scan failed: %w", err) } // Распарсить JSON массив aliases aliasesStr := parseAliasesJSON(aliasesJSON) - if _, exists := clients[clientTitle]; !exists { - clients[clientTitle] = ClientData{ - ClientTitle: clientTitle, + // Если задан parent_client_title — группировать по нему + groupKey := clientTitle + if parentClientTitle != "" { + groupKey = parentClientTitle + } + + if _, exists := clients[groupKey]; !exists { + clients[groupKey] = ClientData{ + ClientTitle: groupKey, Domains: []DomainInfo{}, RPSLimit: rpsLimit, RPSCommercialLimit: rpsCommercialLimit, WafProvider: wafProvider, PtafFallbackCode: ptafFallbackCode, } + } else { + // Обновляем лимиты — берём максимальные по группе + existing := clients[groupKey] + if rpsLimit > existing.RPSLimit { + existing.RPSLimit = rpsLimit + } + if rpsCommercialLimit > existing.RPSCommercialLimit { + existing.RPSCommercialLimit = rpsCommercialLimit + } + clients[groupKey] = existing } - client := clients[clientTitle] + client := clients[groupKey] client.Domains = append(client.Domains, DomainInfo{ SID: sid, DomainName: domainName, @@ -93,7 +110,7 @@ func fetchClientsData(db *sql.DB) (map[string]ClientData, error) { Limit5xx: limit5xx, PtafFallbackCodeAlertCount: ptafFallbackCodeAlertCount, }) - clients[clientTitle] = client + clients[groupKey] = client } return clients, rows.Err() diff --git a/env.go b/env.go old mode 100644 new mode 100755 diff --git a/git.go b/git.go old mode 100644 new mode 100755 diff --git a/go.mod b/go.mod old mode 100644 new mode 100755 diff --git a/go.sum b/go.sum old mode 100644 new mode 100755 diff --git a/grafana.go b/grafana.go old mode 100644 new mode 100755 diff --git a/main.go b/main.go old mode 100644 new mode 100755 diff --git a/panels.go b/panels.go old mode 100644 new mode 100755 diff --git a/panels_vl.go b/panels_vl.go old mode 100644 new mode 100755 diff --git a/state.go b/state.go old mode 100644 new mode 100755 diff --git a/template.go b/template.go old mode 100644 new mode 100755 diff --git a/template_validator.go b/template_validator.go old mode 100644 new mode 100755