20 KiB
dashboard_template.json — Руководство по созданию шаблонов
Шаблон полностью управляет структурой и содержимым генерируемого дашборда Grafana. Go-код утилиты является исполнителем инструкций из шаблона — добавление новых панелей не требует изменения кода.
Структура файла
{
"version": "3.0.0",
"description": "Описание шаблона",
"datasource": { ... },
"dashboard_meta": { ... },
"dashboard": { ... },
"layout": { ... },
"panels": { ... },
"queries": { ... }
}
| Секция | Назначение |
|---|---|
version |
Версия шаблона. Изменение вызывает регенерацию дашборда |
description |
Произвольное описание, не используется в генерации |
datasource |
Тип и UID источника данных Grafana |
dashboard_meta |
Метаданные дашборда: UID, теги, версия |
dashboard |
Настройки дашборда Grafana (аннотации, тайм-зона, время по умолчанию) |
layout |
Расположение панелей: какие панели и в каком порядке показывать |
panels |
Определения панелей: внешний вид + логика запросов |
queries |
Переиспользуемые конфигурации запросов |
Секция datasource
Задаёт источник данных по умолчанию для всех панелей.
"datasource": {
"type": "grafana-opensearch-datasource",
"uid": "af84zsvlp9blsa"
}
| Поле | Описание |
|---|---|
type |
Тип плагина datasource в Grafana |
uid |
UID источника данных (найти в Grafana → Connections → Data sources) |
Секция dashboard_meta
Метаданные создаваемого дашборда. Позволяет управлять UID и тегами без изменения Go-кода.
"dashboard_meta": {
"uid": "pt-af-requests-auto",
"tags": ["auto-generated", "waf-monitoring"],
"version": 0
}
| Поле | Описание |
|---|---|
uid |
Статичный UID дашборда. Именно по нему Grafana определяет, обновить существующий дашборд или создать новый. Менять с осторожностью — при смене UID будет создан новый дашборд |
tags |
Список тегов. Используются для поиска и фильтрации в Grafana |
version |
Начальная версия. Обычно 0 — Grafana сама увеличивает при каждом обновлении |
Секция dashboard
Стандартные настройки дашборда Grafana. Копируются в создаваемый дашборд как есть.
"dashboard": {
"editable": true,
"timezone": "browser",
"time": {"from": "now-6h", "to": "now"},
"graphTooltip": 0,
"schemaVersion": 39
}
Полный список доступных полей соответствует Grafana Dashboard JSON Model. Наиболее часто меняемые:
| Поле | Значения | Описание |
|---|---|---|
timezone |
"browser", "utc", "Europe/Moscow" |
Часовой пояс |
time.from |
"now-6h", "now-24h", "now-7d" |
Начало временного диапазона по умолчанию |
time.to |
"now" |
Конец диапазона |
editable |
true / false |
Разрешить редактирование дашборда в UI |
graphTooltip |
0 — отдельный, 1 — общий, 2 — зафиксированный |
Режим тултипа |
Секция layout
Определяет какие панели генерировать для каждого клиента и каждого домена, и где их размещать.
"layout": {
"use_collapsed_rows": true,
"tenant_panels": [ ... ],
"domain_panels": [ ... ]
}
| Поле | Описание |
|---|---|
use_collapsed_rows |
true — каждый клиент в свёрнутой строке (все панели внутри row). false — панели размещаются последовательно на дашборде |
tenant_panels |
Панели на уровне клиента (агрегируют данные по всем доменам клиента) |
domain_panels |
Панели на уровне домена (отдельно для каждого ресурса клиента) |
Структура элемента layout
{
"panel_key": "rps",
"title_format": "RPS %s",
"width": 12,
"x_offset": 0,
"y_offset": 1,
"condition": null
}
| Поле | Обязательно | Описание |
|---|---|---|
panel_key |
✅ | Ключ панели из секции panels |
width |
✅ | Ширина панели (1–24, сетка Grafana — 24 колонки) |
x_offset |
✅ | Позиция по горизонтали (0–23) |
y_offset |
— | Позиция по вертикали внутри collapsed row |
title_format |
— | Формат заголовка с плейсхолдерами |
title_suffix |
— | Суффикс к имени домена в заголовке |
condition |
— | Условие отображения панели |
Позиционирование
Grafana использует сетку шириной 24 единицы. Типичные варианты размещения:
Две панели по полэкрана:
panel_1: width=12, x_offset=0
panel_2: width=12, x_offset=12
Одна широкая панель:
panel: width=24, x_offset=0
Три панели:
panel_1: width=8, x_offset=0
panel_2: width=8, x_offset=8
panel_3: width=8, x_offset=16
Секция panels
Библиотека панелей. Каждая панель описывает внешний вид (Grafana JSON) и логику запросов (query_mode + query_config).
"panels": {
"row": { ... },
"rps": { ... },
"status_codes": { ... },
"my_new_panel": { ... }
}
Ключ (например "rps") используется в layout как panel_key.
Структура панели
"my_panel": {
"type": "timeseries",
"description": "Описание панели",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"fieldConfig": { ... },
"options": { ... },
"query_mode": "multi_target",
"query_config": { ... }
}
Поля делятся на две категории:
Grafana-поля (передаются в API как есть): type, description, gridPos, fieldConfig, options, transformations, overrides и любые другие стандартные поля Grafana.
Служебные поля шаблона (используются при генерации, не попадают в итоговый JSON): query_mode, query_config.
Совет: проще всего создать нужную панель вручную в Grafana, экспортировать дашборд через Share → Export, скопировать JSON панели и добавить к нему
query_modeиquery_config.
Специальная панель row
Обязательная панель для создания строк-заголовков клиентов:
"row": {
"type": "row",
"collapsed": true,
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 0},
"panels": []
}
Не требует query_mode. Заголовок (title) устанавливается автоматически из имени клиента.
Секция queries
Переиспользуемые конфигурации bucket aggregation для OpenSearch/Elasticsearch.
"queries": {
"bucket_agg": {
"field": "@timestamp",
"id": "2",
"settings": {"interval": "1m", "trimEdges": "1"},
"type": "date_histogram"
}
}
bucket_agg используется автоматически во всех построителях targets как агрегация по времени. Изменение interval здесь влияет на разрешение всех графиков.
query_mode — типы запросов
query_mode определяет как будут построены targets панели. Выбирается в зависимости от того, что нужно отобразить.
rps — Запросы в секунду
Один count-запрос + math expression $A / 60 для перевода из запросов/минуту в запросы/секунду. Предназначен для tenant-уровня (агрегация по всем SID клиента).
"query_mode": "rps",
"query_config": {
"base_query": "SID:({sids})",
"alias": "{client_title} Total RPS",
"expression": "$A / 60",
"expression_ref_id": "RPS"
}
| Поле | Описание |
|---|---|
base_query |
Lucene-запрос. Плейсхолдер {sids} заменяется на список SID через OR |
alias |
Имя серии на графике. Поддерживает {client_title} |
expression |
Math expression Grafana. $A ссылается на data query |
expression_ref_id |
refId результирующей серии |
multi_target — Несколько фильтров
Несколько самостоятельных запросов с разными условиями. Используется для статус-кодов, разбивки по любому признаку.
"query_mode": "multi_target",
"query_config": {
"base_query": "SID:{sid}",
"metric": "count",
"targets": [
{"alias": "2**", "query_suffix": "AND response_status_code:[200 TO 299]", "ref_id": "A"},
{"alias": "3**", "query_suffix": "AND response_status_code:[300 TO 399]", "ref_id": "B"},
{"alias": "4**", "query_suffix": "AND response_status_code:[400 TO 499]", "ref_id": "C"},
{"alias": "5**", "query_suffix": "AND response_status_code:[500 TO 599]", "ref_id": "D"}
]
}
| Поле | Описание |
|---|---|
base_query |
Базовый Lucene-запрос. {sid} заменяется на SID ресурса |
metric |
Тип агрегации: "count" |
targets[].alias |
Имя серии |
targets[].query_suffix |
Дополнение к base_query через AND |
targets[].ref_id |
Уникальный идентификатор запроса (A, B, C...) |
range_percent — Распределение по диапазонам в %
Скрытый total-запрос + запросы по диапазонам + math expressions ($X / $Total) * 100. Используется для распределения времени ответа, размеров ответов и т.п.
"query_mode": "range_percent",
"query_config": {
"base_query": "SID:{sid}",
"field_filter": "upstream_response_time_digital:[0 TO *]",
"total_ref_id": "F",
"range_field": "upstream_response_time_digital",
"ranges": [
{"label": "0-0.3s", "from": "0", "to": "0.3", "ref_id": "A"},
{"label": "0.3-1s", "from": "0.3", "to": "1", "ref_id": "B"},
{"label": "1-3s", "from": "1", "to": "3", "ref_id": "C"},
{"label": "3-10s", "from": "3", "to": "10", "ref_id": "D"},
{"label": "10s+", "from": "10", "to": "*", "ref_id": "E"}
]
}
| Поле | Описание |
|---|---|
field_filter |
Фильтр для total-запроса (исключает записи без нужного поля) |
total_ref_id |
refId total-запроса, на который делится каждый диапазон |
range_field |
Поле OpenSearch по которому строятся диапазоны |
ranges[].label |
Подпись на графике |
ranges[].from |
Начало диапазона (Lucene range syntax) |
ranges[].to |
Конец диапазона. "*" означает неограниченно |
ranges[].ref_id |
refId data-запроса |
Диапазоны можно добавлять/убирать не меняя Go-код.
multi_sum_expression — Несколько метрик суммирования
Несколько sum-запросов, каждый с math expression. Используется для отображения нескольких потоков трафика на одном графике.
"query_mode": "multi_sum_expression",
"query_config": {
"base_query": "SID:{sid}",
"targets": [
{
"alias": "Response Traffic",
"field": "upstream_response_length_digital",
"ref_id": "A",
"expression": "$A / 60",
"expression_ref_id": "ResponseTraffic"
},
{
"alias": "Request Traffic",
"field": "upstream_bytes_sent_digital",
"ref_id": "B",
"expression": "$B / 60",
"expression_ref_id": "RequestTraffic"
}
]
}
| Поле | Описание |
|---|---|
targets[].alias |
Имя серии исходного запроса |
targets[].field |
Поле OpenSearch для sum-агрегации |
targets[].ref_id |
refId data-запроса |
targets[].expression |
Math expression. $A ссылается на ref_id |
targets[].expression_ref_id |
refId результирующей серии (отображается в легенде) |
sum_expression — Одна метрика суммирования
Упрощённый вариант multi_sum_expression для одной метрики.
"query_mode": "sum_expression",
"query_config": {
"base_query": "SID:{sid}",
"field": "upstream_response_length_digital",
"ref_id": "A",
"expression": "$A / 60",
"expression_ref_id": "Traffic"
}
bucket_logs — Произвольные bucket aggregations
Полный контроль над агрегациями OpenSearch. Используется для barchart, таблиц, любых нестандартных агрегаций.
"query_mode": "bucket_logs",
"query_config": {
"base_query": "log_type:access AND SID:{sid}",
"bucket_aggs": [
{
"field": "response_status_code",
"id": "3",
"settings": {"min_doc_count": "1", "order": "desc", "orderBy": "_count", "size": "10"},
"type": "terms"
},
{
"field": "@timestamp",
"id": "2",
"settings": {"interval": "1m", "trimEdges": "1"},
"type": "date_histogram"
}
]
}
| Поле | Описание |
|---|---|
base_query |
Lucene-запрос. {sid} заменяется на SID ресурса |
bucket_aggs |
Массив bucket aggregations в формате OpenSearch Grafana плагина. Порядок важен — внешняя агрегация первая |
Плейсхолдеры в заголовках
Заголовки панелей задаются через title_format или title_suffix в секции layout.
title_format
Полный контроль над заголовком. Поддерживает плейсхолдеры:
| Плейсхолдер | Значение |
|---|---|
{client} |
Название клиента |
{domain} |
Доменное имя ресурса |
{sid} |
SID ресурса |
%s |
Название клиента (fmt.Sprintf, для обратной совместимости) |
Примеры:
"title_format": "RPS {client}"
"title_format": "RPS %s"
"title_format": "{client} / {domain}"
title_suffix
Суффикс, который добавляется к имени домена: "<domain> <suffix>".
Тоже поддерживает плейсхолдеры:
"title_suffix": "{sid} Status Codes"
// Результат: "example.com sid123 Status Codes"
"title_suffix": "Traffic"
// Результат: "example.com Traffic"
Приоритет
Если в layout задан title_format — используется он. Если только title_suffix — используется суффикс. Если не задано ничего — используется title из определения панели в секции panels.
Условия отображения панелей
Поле condition в элементе layout позволяет показывать панель только при выполнении условия.
{
"panel_key": "rps",
"width": 12,
"x_offset": 0,
"condition": {
"type": "min_domains",
"value": 2
}
}
Доступные типы условий
min_domains — минимальное количество доменов
Показывает панель только если у клиента не менее N доменов.
"condition": {"type": "min_domains", "value": 3}
max_domains — максимальное количество доменов
Показывает панель только если у клиента не более N доменов.
"condition": {"type": "max_domains", "value": 1}
client_name_matches — фильтр по имени клиента
Показывает панель только для клиентов, в имени которых содержится указанная строка.
"condition": {"type": "client_name_matches", "value": "Enterprise"}
Если condition не задан или равен null — панель отображается всегда.
Как добавить новую панель
Шаг 1. Определить панель в секции panels
"panels": {
"blocked_requests": {
"type": "timeseries",
"description": "Заблокированные запросы WAF",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {
"drawStyle": "line",
"fillOpacity": 10,
"lineWidth": 1
}
}
},
"options": {
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true}
},
"query_mode": "multi_target",
"query_config": {
"base_query": "SID:{sid}",
"metric": "count",
"targets": [
{"alias": "Blocked", "query_suffix": "AND action:block", "ref_id": "A"},
{"alias": "Passed", "query_suffix": "AND action:pass", "ref_id": "B"}
]
}
}
}
Шаг 2. Добавить в layout
"domain_panels": [
{
"panel_key": "blocked_requests",
"title_suffix": "{sid} WAF Actions",
"width": 12,
"x_offset": 0,
"y_offset": 25
}
]
Этого достаточно. Пересборка бинарника не нужна.
Если нужен принципиально новый тип запроса
Если существующие query_mode не покрывают нужный случай — добавьте в panels.go:
// 1. Функция-строитель targets
func buildMyModeTargets(cfg map[string]interface{}, sidQuery, ...) []interface{} {
// логика построения targets
}
// 2. Регистрация в реестре (одна строка)
var queryModeRegistry = map[string]targetBuilder{
// ...существующие...
"my_mode": buildMyModeTargets,
}
После этого "query_mode": "my_mode" становится доступен в шаблоне.