From d62ca6b33f3e47adb7741b0ce89e7744815d3be8 Mon Sep 17 00:00:00 2001 From: Magnus Root Date: Mon, 23 Mar 2026 15:39:07 +0300 Subject: [PATCH] Added checker --- main.go | 1 + template_validator.go | 144 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 template_validator.go diff --git a/main.go b/main.go index 1432339..218df2b 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ func main() { } log.Printf("Loaded template version: %s", template.Version) + validateAndFixTemplate(template) // Load alert rules template alertTmpl, err := loadAlertTemplate(config.TemplatesPath) diff --git a/template_validator.go b/template_validator.go new file mode 100644 index 0000000..af2ab57 --- /dev/null +++ b/template_validator.go @@ -0,0 +1,144 @@ +package main + +import "log" + +// validateAndFixTemplate проверяет критичные настройки шаблона и исправляет их если нужно. +// Вызывается после loadTemplate чтобы гарантировать корректность шаблона независимо от его источника. +func validateAndFixTemplate(t *DashboardTemplate) { + fixed := 0 + + // --- vl_interval для панелей с агрегацией --- + vlIntervalPanels := []string{"rps", "rps_multi", "status_codes", "response_time", "traffic_combined", "traffic_combined_total"} + for _, key := range vlIntervalPanels { + p, ok := t.Panels[key] + if !ok { + continue + } + pm, ok := p.(map[string]interface{}) + if !ok { + continue + } + if v, _ := pm["vl_interval"].(string); v == "" { + pm["vl_interval"] = "1m" + log.Printf("[template-validator] Fixed: panels.%s.vl_interval = \"1m\"", key) + fixed++ + } + } + + // --- spanNulls для status_codes --- + if p, ok := t.Panels["status_codes"]; ok { + if pm, ok := p.(map[string]interface{}); ok { + if fc, ok := pm["fieldConfig"].(map[string]interface{}); ok { + if defaults, ok := fc["defaults"].(map[string]interface{}); ok { + if custom, ok := defaults["custom"].(map[string]interface{}); ok { + if v, _ := custom["spanNulls"].(bool); !v { + custom["spanNulls"] = true + log.Printf("[template-validator] Fixed: panels.status_codes.fieldConfig.defaults.custom.spanNulls = true") + fixed++ + } + } + } + } + } + } + + // --- vl_base_query для overview_tenant_per_nodes --- + checkOverviewVLQuery(t, "overview_tenant_per_nodes", + "log_type:access TENANT:in(${tenant:csv})", + "log_type:access AND TENANT:${tenant:lucene}", + &fixed, + ) + + // --- vl_base_query для overview_tenant_per_node --- + checkOverviewVLQuery(t, "overview_tenant_per_node", + "log_type:access node_name:in(${node_name:csv})", + "log_type:access AND node_name:${node_name:lucene}", + &fixed, + ) + + // --- layout: три столбца --- + checkLayout(t, &fixed) + + if fixed > 0 { + log.Printf("[template-validator] Total fixes applied: %d", fixed) + } else { + log.Printf("[template-validator] Template OK, no fixes needed") + } +} + +func checkOverviewVLQuery(t *DashboardTemplate, panelKey, expectedVL, expectedOS string, fixed *int) { + p, ok := t.Panels[panelKey] + if !ok { + return + } + pm, ok := p.(map[string]interface{}) + if !ok { + return + } + qc, ok := pm["query_config"].(map[string]interface{}) + if !ok { + return + } + if v, _ := qc["vl_base_query"].(string); v == "" { + qc["vl_base_query"] = expectedVL + log.Printf("[template-validator] Fixed: panels.%s.query_config.vl_base_query", panelKey) + (*fixed)++ + } + if v, _ := qc["base_query"].(string); v == "log_type:access" { + qc["base_query"] = expectedOS + log.Printf("[template-validator] Fixed: panels.%s.query_config.base_query", panelKey) + (*fixed)++ + } +} + +func checkLayout(t *DashboardTemplate, fixed *int) { + // Проверяем что tenant_panels имеет три столбца по 8 + tenantPanels := t.Layout.TenantPanels + hasTraffic := false + wrongWidth := false + for _, p := range tenantPanels { + if p.PanelKey == "traffic_combined_total" { + hasTraffic = true + } + if p.Width == 12 { + wrongWidth = true + } + } + + if !hasTraffic || wrongWidth { + log.Printf("[template-validator] Fixed: layout.tenant_panels restored to 3-column (8+8+8)") + t.Layout.TenantPanels = []PanelLayout{ + {PanelKey: "status_codes", TitleFormat: "Status Codes {client}", Width: 8, XOffset: 0}, + {PanelKey: "rps", TitleFormat: "RPS {client}", Width: 8, XOffset: 8, + Condition: &PanelCondition{Type: "max_domains", Value: 1}}, + {PanelKey: "rps_multi", TitleFormat: "RPS {client}", Width: 8, XOffset: 8, + Condition: &PanelCondition{Type: "min_domains", Value: 2}}, + {PanelKey: "traffic_combined_total", TitleFormat: "Total Traffic {client}", Width: 8, XOffset: 16, + Condition: &PanelCondition{Type: "min_domains", Value: 1}}, + } + (*fixed)++ + } + + // Проверяем domain_panels + domainPanels := t.Layout.DomainPanels + hasDomainTraffic := false + domainWrongWidth := false + for _, p := range domainPanels { + if p.PanelKey == "traffic_combined" { + hasDomainTraffic = true + } + if p.Width == 12 { + domainWrongWidth = true + } + } + + if !hasDomainTraffic || domainWrongWidth { + log.Printf("[template-validator] Fixed: layout.domain_panels restored to 3-column (8+8+8)") + t.Layout.DomainPanels = []PanelLayout{ + {PanelKey: "status_codes", TitleSuffix: "{sid} Status Codes", Width: 8, XOffset: 0}, + {PanelKey: "response_time", TitleSuffix: "{sid} Response Time", Width: 8, XOffset: 8}, + {PanelKey: "traffic_combined", TitleFormat: "{domain} {sid} Traffic", Width: 8, XOffset: 16}, + } + (*fixed)++ + } +}