313 lines
11 KiB
Go
313 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
)
|
|
|
|
// targetBuilder — тип функции-строителя targets для конкретного query_mode.
|
|
type targetBuilder func(cfg map[string]interface{}, sidQuery string, datasourceType, datasourceUID string, template *DashboardTemplate, domain DomainInfo, client ClientData) []interface{}
|
|
|
|
// queryModeRegistry — реестр строителей targets по query_mode.
|
|
// Чтобы добавить новый тип панели в Go: одна строка здесь + функция ниже.
|
|
// Всё остальное — только в JSON.
|
|
var queryModeRegistry = map[string]targetBuilder{
|
|
"rps": buildRPSTargets,
|
|
"multi_target": buildMultiTargets,
|
|
"range_percent": buildRangePercentTargets,
|
|
"multi_sum_expression": buildMultiSumExpressionTargets,
|
|
"sum_expression": buildSumExpressionTargets,
|
|
"bucket_logs": buildBucketLogsTargets,
|
|
}
|
|
|
|
// conditionEvaluator — тип функции-вычислителя условия отображения панели.
|
|
type conditionEvaluator func(value interface{}, client ClientData, domain *DomainInfo) bool
|
|
|
|
// conditionRegistry — реестр вычислителей условий по типу.
|
|
// Чтобы добавить новый тип условия: одна строка здесь + лямбда.
|
|
var conditionRegistry = map[string]conditionEvaluator{
|
|
"min_domains": func(value interface{}, client ClientData, _ *DomainInfo) bool {
|
|
return len(client.Domains) >= int(value.(float64))
|
|
},
|
|
"max_domains": func(value interface{}, client ClientData, _ *DomainInfo) bool {
|
|
return len(client.Domains) <= int(value.(float64))
|
|
},
|
|
"client_name_matches": func(value interface{}, client ClientData, _ *DomainInfo) bool {
|
|
return strings.Contains(client.ClientTitle, value.(string))
|
|
},
|
|
}
|
|
|
|
// buildPanel — универсальная функция построения панели.
|
|
// Тип панели и логика запросов полностью определяются полем query_mode в JSON-шаблоне.
|
|
func buildPanel(
|
|
panelKey string,
|
|
template *DashboardTemplate,
|
|
domain DomainInfo,
|
|
client ClientData,
|
|
datasourceType, datasourceUID string,
|
|
id, yPos, xPos, width int,
|
|
) map[string]interface{} {
|
|
panelDef, ok := template.Panels[panelKey]
|
|
if !ok {
|
|
log.Printf("Warning: panel '%s' not found in template", panelKey)
|
|
return nil
|
|
}
|
|
|
|
panelMap, ok := panelDef.(map[string]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
queryMode, _ := panelMap["query_mode"].(string)
|
|
if queryMode == "" {
|
|
log.Printf("Warning: panel '%s' has no query_mode", panelKey)
|
|
return nil
|
|
}
|
|
|
|
builder, ok := queryModeRegistry[queryMode]
|
|
if !ok {
|
|
log.Printf("Warning: unknown query_mode '%s' for panel '%s'. Available: %v", queryMode, panelKey, registeredModes())
|
|
return nil
|
|
}
|
|
|
|
queryConfigRaw, _ := panelMap["query_config"].(map[string]interface{})
|
|
panel := copyPanelBase(panelMap, id, yPos, xPos, width)
|
|
sidQuery := buildSIDQuery(domain.SID)
|
|
|
|
panel["targets"] = builder(queryConfigRaw, sidQuery, datasourceType, datasourceUID, template, domain, client)
|
|
panel["datasource"] = map[string]interface{}{
|
|
"type": datasourceType,
|
|
"uid": datasourceUID,
|
|
}
|
|
|
|
return panel
|
|
}
|
|
|
|
// evaluateCondition проверяет условие отображения панели через реестр
|
|
func evaluateCondition(condition *PanelCondition, client ClientData, domain *DomainInfo) bool {
|
|
if condition == nil {
|
|
return true
|
|
}
|
|
evaluator, ok := conditionRegistry[condition.Type]
|
|
if !ok {
|
|
log.Printf("Warning: unknown condition type '%s'. Available: %v", condition.Type, registeredConditions())
|
|
return true
|
|
}
|
|
return evaluator(condition.Value, client, domain)
|
|
}
|
|
|
|
func registeredModes() []string {
|
|
modes := make([]string, 0, len(queryModeRegistry))
|
|
for k := range queryModeRegistry {
|
|
modes = append(modes, k)
|
|
}
|
|
return modes
|
|
}
|
|
|
|
func registeredConditions() []string {
|
|
types := make([]string, 0, len(conditionRegistry))
|
|
for k := range conditionRegistry {
|
|
types = append(types, k)
|
|
}
|
|
return types
|
|
}
|
|
|
|
// copyPanelBase копирует визуальную часть панели, убирая служебные поля шаблона
|
|
func copyPanelBase(panelMap map[string]interface{}, id, yPos, xPos, width int) map[string]interface{} {
|
|
panel := deepCopy(panelMap).(map[string]interface{})
|
|
delete(panel, "query_mode")
|
|
delete(panel, "query_config")
|
|
|
|
panel["id"] = id
|
|
gridPos, ok := panel["gridPos"].(map[string]interface{})
|
|
if !ok {
|
|
gridPos = map[string]interface{}{}
|
|
panel["gridPos"] = gridPos
|
|
}
|
|
gridPos["y"] = yPos
|
|
gridPos["x"] = xPos
|
|
if width > 0 {
|
|
gridPos["w"] = width
|
|
}
|
|
return panel
|
|
}
|
|
|
|
func buildSIDQuery(sid string) string {
|
|
if strings.Contains(sid, " OR ") {
|
|
return fmt.Sprintf("SID:(%s)", sid)
|
|
}
|
|
return fmt.Sprintf("SID:%s", sid)
|
|
}
|
|
|
|
func replacePlaceholders(s, sid, clientTitle string) string {
|
|
s = strings.ReplaceAll(s, "{sid}", sid)
|
|
s = strings.ReplaceAll(s, "{sids}", sid)
|
|
s = strings.ReplaceAll(s, "{client_title}", clientTitle)
|
|
return s
|
|
}
|
|
|
|
func getBucketAgg(template *DashboardTemplate) map[string]interface{} {
|
|
return deepCopy(template.Queries["bucket_agg"]).(map[string]interface{})
|
|
}
|
|
|
|
func makeDataTarget(alias, query, refID string, metric map[string]interface{}, hidden bool, datasourceType, datasourceUID string, bucketAgg map[string]interface{}) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"alias": alias,
|
|
"bucketAggs": []interface{}{
|
|
map[string]interface{}{
|
|
"field": bucketAgg["field"],
|
|
"id": "2",
|
|
"settings": bucketAgg["settings"],
|
|
"type": bucketAgg["type"],
|
|
},
|
|
},
|
|
"datasource": map[string]interface{}{"type": datasourceType, "uid": datasourceUID},
|
|
"format": "table",
|
|
"hide": hidden,
|
|
"luceneQueryType": "Metric",
|
|
"metrics": []interface{}{metric},
|
|
"query": query,
|
|
"queryType": "lucene",
|
|
"refId": refID,
|
|
"timeField": "@timestamp",
|
|
}
|
|
}
|
|
|
|
func makeMathTarget(expression, refID string) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"datasource": map[string]interface{}{
|
|
"name": "Expression",
|
|
"type": "__expr__",
|
|
"uid": "__expr__",
|
|
},
|
|
"expression": expression,
|
|
"hide": false,
|
|
"refId": refID,
|
|
"type": "math",
|
|
}
|
|
}
|
|
|
|
func getString(m map[string]interface{}, key string) string {
|
|
if v, ok := m[key].(string); ok {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// --- Строители targets ---
|
|
|
|
func buildRPSTargets(cfg map[string]interface{}, sidQuery, datasourceType, datasourceUID string, template *DashboardTemplate, _ DomainInfo, client ClientData) []interface{} {
|
|
query := replacePlaceholders(getString(cfg, "base_query"), sidQuery, client.ClientTitle)
|
|
alias := replacePlaceholders(getString(cfg, "alias"), sidQuery, client.ClientTitle)
|
|
bucketAgg := getBucketAgg(template)
|
|
countMetric := map[string]interface{}{"id": "1", "type": "count"}
|
|
|
|
return []interface{}{
|
|
makeDataTarget(alias, query, "A", countMetric, true, datasourceType, datasourceUID, bucketAgg),
|
|
makeMathTarget(getString(cfg, "expression"), getString(cfg, "expression_ref_id")),
|
|
}
|
|
}
|
|
|
|
func buildMultiTargets(cfg map[string]interface{}, sidQuery, datasourceType, datasourceUID string, template *DashboardTemplate, _ DomainInfo, _ ClientData) []interface{} {
|
|
targetsRaw, _ := cfg["targets"].([]interface{})
|
|
bucketAgg := getBucketAgg(template)
|
|
countMetric := map[string]interface{}{"id": "1", "type": "count"}
|
|
|
|
targets := []interface{}{}
|
|
for _, t := range targetsRaw {
|
|
tMap, ok := t.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
query := sidQuery
|
|
if suffix := getString(tMap, "query_suffix"); suffix != "" {
|
|
query = sidQuery + " " + suffix
|
|
}
|
|
targets = append(targets, makeDataTarget(
|
|
getString(tMap, "alias"), query, getString(tMap, "ref_id"),
|
|
countMetric, false, datasourceType, datasourceUID, bucketAgg,
|
|
))
|
|
}
|
|
return targets
|
|
}
|
|
|
|
func buildRangePercentTargets(cfg map[string]interface{}, sidQuery, datasourceType, datasourceUID string, template *DashboardTemplate, _ DomainInfo, _ ClientData) []interface{} {
|
|
fieldFilter := getString(cfg, "field_filter")
|
|
totalRefID := getString(cfg, "total_ref_id")
|
|
rangeField := getString(cfg, "range_field")
|
|
rangesRaw, _ := cfg["ranges"].([]interface{})
|
|
bucketAgg := getBucketAgg(template)
|
|
countMetric := map[string]interface{}{"id": "1", "type": "count"}
|
|
|
|
totalQuery := sidQuery
|
|
if fieldFilter != "" {
|
|
totalQuery = sidQuery + " AND " + fieldFilter
|
|
}
|
|
|
|
targets := []interface{}{
|
|
makeDataTarget("Total", totalQuery, totalRefID, countMetric, true, datasourceType, datasourceUID, bucketAgg),
|
|
}
|
|
|
|
for _, r := range rangesRaw {
|
|
rMap, ok := r.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
label := getString(rMap, "label")
|
|
refID := getString(rMap, "ref_id")
|
|
query := fmt.Sprintf("%s AND %s:[%s TO %s]", sidQuery, rangeField, getString(rMap, "from"), getString(rMap, "to"))
|
|
|
|
targets = append(targets,
|
|
makeDataTarget(label, query, refID, countMetric, true, datasourceType, datasourceUID, bucketAgg),
|
|
makeMathTarget(fmt.Sprintf("($%s / $%s) * 100", refID, totalRefID), label),
|
|
)
|
|
}
|
|
return targets
|
|
}
|
|
|
|
func buildMultiSumExpressionTargets(cfg map[string]interface{}, sidQuery, datasourceType, datasourceUID string, template *DashboardTemplate, _ DomainInfo, _ ClientData) []interface{} {
|
|
targetsRaw, _ := cfg["targets"].([]interface{})
|
|
bucketAgg := getBucketAgg(template)
|
|
|
|
targets := []interface{}{}
|
|
for _, t := range targetsRaw {
|
|
tMap, ok := t.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
sumMetric := map[string]interface{}{"field": getString(tMap, "field"), "id": "1", "type": "sum"}
|
|
targets = append(targets,
|
|
makeDataTarget(getString(tMap, "alias"), sidQuery, getString(tMap, "ref_id"), sumMetric, true, datasourceType, datasourceUID, bucketAgg),
|
|
makeMathTarget(getString(tMap, "expression"), getString(tMap, "expression_ref_id")),
|
|
)
|
|
}
|
|
return targets
|
|
}
|
|
|
|
func buildSumExpressionTargets(cfg map[string]interface{}, sidQuery, datasourceType, datasourceUID string, template *DashboardTemplate, _ DomainInfo, _ ClientData) []interface{} {
|
|
bucketAgg := getBucketAgg(template)
|
|
sumMetric := map[string]interface{}{"field": getString(cfg, "field"), "id": "1", "type": "sum"}
|
|
|
|
return []interface{}{
|
|
makeDataTarget("", sidQuery, getString(cfg, "ref_id"), sumMetric, true, datasourceType, datasourceUID, bucketAgg),
|
|
makeMathTarget(getString(cfg, "expression"), getString(cfg, "expression_ref_id")),
|
|
}
|
|
}
|
|
|
|
func buildBucketLogsTargets(cfg map[string]interface{}, sidQuery, datasourceType, datasourceUID string, _ *DashboardTemplate, _ DomainInfo, _ ClientData) []interface{} {
|
|
query := strings.ReplaceAll(getString(cfg, "base_query"), "{sid}", sidQuery)
|
|
return []interface{}{
|
|
map[string]interface{}{
|
|
"bucketAggs": deepCopy(cfg["bucket_aggs"]),
|
|
"datasource": map[string]interface{}{"type": datasourceType, "uid": datasourceUID},
|
|
"format": "table",
|
|
"luceneQueryType": "Logs",
|
|
"metrics": []interface{}{map[string]interface{}{"id": "1", "type": "count"}},
|
|
"query": query,
|
|
"queryType": "lucene",
|
|
"refId": "A",
|
|
"timeField": "@timestamp",
|
|
},
|
|
}
|
|
}
|