grafana_gen_template/README.md
2026-06-04 12:06:21 +03:00

568 lines
20 KiB
Markdown
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# dashboard_template.json — Руководство по созданию шаблонов
Шаблон полностью управляет структурой и содержимым генерируемого дашборда Grafana. Go-код утилиты является исполнителем инструкций из шаблона — добавление новых панелей не требует изменения кода.
---
## Структура файла
```json
{
"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
Задаёт источник данных по умолчанию для всех панелей.
```json
"datasource": {
"type": "grafana-opensearch-datasource",
"uid": "af84zsvlp9blsa"
}
```
| Поле | Описание |
|------|----------|
| `type` | Тип плагина datasource в Grafana |
| `uid` | UID источника данных (найти в Grafana → Connections → Data sources) |
---
## Секция dashboard_meta
Метаданные создаваемого дашборда. Позволяет управлять UID и тегами без изменения Go-кода.
```json
"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. Копируются в создаваемый дашборд как есть.
```json
"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
Определяет какие панели генерировать для каждого клиента и каждого домена, и где их размещать.
```json
"layout": {
"use_collapsed_rows": true,
"tenant_panels": [ ... ],
"domain_panels": [ ... ]
}
```
| Поле | Описание |
|------|----------|
| `use_collapsed_rows` | `true` — каждый клиент в свёрнутой строке (все панели внутри row). `false` — панели размещаются последовательно на дашборде |
| `tenant_panels` | Панели на уровне клиента (агрегируют данные по всем доменам клиента) |
| `domain_panels` | Панели на уровне домена (отдельно для каждого ресурса клиента) |
### Структура элемента layout
```json
{
"panel_key": "rps",
"title_format": "RPS %s",
"width": 12,
"x_offset": 0,
"y_offset": 1,
"condition": null
}
```
| Поле | Обязательно | Описание |
|------|-------------|----------|
| `panel_key` | ✅ | Ключ панели из секции `panels` |
| `width` | ✅ | Ширина панели (124, сетка Grafana — 24 колонки) |
| `x_offset` | ✅ | Позиция по горизонтали (023) |
| `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`).
```json
"panels": {
"row": { ... },
"rps": { ... },
"status_codes": { ... },
"my_new_panel": { ... }
}
```
Ключ (например `"rps"`) используется в `layout` как `panel_key`.
### Структура панели
```json
"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
Обязательная панель для создания строк-заголовков клиентов:
```json
"row": {
"type": "row",
"collapsed": true,
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 0},
"panels": []
}
```
Не требует `query_mode`. Заголовок (`title`) устанавливается автоматически из имени клиента.
---
## Секция queries
Переиспользуемые конфигурации bucket aggregation для OpenSearch/Elasticsearch.
```json
"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 клиента).
```json
"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` — Несколько фильтров
Несколько самостоятельных запросов с разными условиями. Используется для статус-кодов, разбивки по любому признаку.
```json
"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`. Используется для распределения времени ответа, размеров ответов и т.п.
```json
"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. Используется для отображения нескольких потоков трафика на одном графике.
```json
"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` для одной метрики.
```json
"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, таблиц, любых нестандартных агрегаций.
```json
"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, для обратной совместимости) |
Примеры:
```json
"title_format": "RPS {client}"
"title_format": "RPS %s"
"title_format": "{client} / {domain}"
```
### title_suffix
Суффикс, который добавляется к имени домена: `"<domain> <suffix>"`.
Тоже поддерживает плейсхолдеры:
```json
"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 позволяет показывать панель только при выполнении условия.
```json
{
"panel_key": "rps",
"width": 12,
"x_offset": 0,
"condition": {
"type": "min_domains",
"value": 2
}
}
```
### Доступные типы условий
#### `min_domains` — минимальное количество доменов
Показывает панель только если у клиента не менее N доменов.
```json
"condition": {"type": "min_domains", "value": 3}
```
#### `max_domains` — максимальное количество доменов
Показывает панель только если у клиента не более N доменов.
```json
"condition": {"type": "max_domains", "value": 1}
```
#### `client_name_matches` — фильтр по имени клиента
Показывает панель только для клиентов, в имени которых содержится указанная строка.
```json
"condition": {"type": "client_name_matches", "value": "Enterprise"}
```
Если `condition` не задан или равен `null` — панель отображается всегда.
---
## Как добавить новую панель
### Шаг 1. Определить панель в секции `panels`
```json
"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
```json
"domain_panels": [
{
"panel_key": "blocked_requests",
"title_suffix": "{sid} WAF Actions",
"width": 12,
"x_offset": 0,
"y_offset": 25
}
]
```
Этого достаточно. Пересборка бинарника не нужна.
### Если нужен принципиально новый тип запроса
Если существующие `query_mode` не покрывают нужный случай — добавьте в `panels.go`:
```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"` становится доступен в шаблоне.