Work version

This commit is contained in:
Magnus Root 2026-02-24 12:54:28 +03:00
commit 505de27f14
2 changed files with 903 additions and 0 deletions

568
README.md Normal file
View file

@ -0,0 +1,568 @@
# 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"` становится доступен в шаблоне.

335
dashboard_template.json Normal file
View file

@ -0,0 +1,335 @@
{
"version": "3.0.0",
"description": "Data-driven template - panels fully describe their query logic",
"datasource": {
"type": "grafana-opensearch-datasource",
"uid": "af84zsvlp9blsa"
},
"dashboard_meta": {
"uid": "pt-af-requests-auto",
"tags": ["auto-generated", "waf-monitoring"],
"version": 0
},
"dashboard": {
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {"type": "grafana", "uid": "-- Grafana --"},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"schemaVersion": 39,
"timezone": "browser",
"time": {"from": "now-6h", "to": "now"},
"timepicker": {},
"weekStart": ""
},
"layout": {
"use_collapsed_rows": true,
"tenant_panels": [
{
"panel_key": "rps",
"title_format": "RPS %s",
"width": 12,
"x_offset": 0,
"y_offset": 1
},
{
"panel_key": "status_codes",
"title_format": "Status Codes %s",
"width": 12,
"x_offset": 12,
"y_offset": 1
}
],
"domain_panels": [
{
"panel_key": "status_codes",
"title_suffix": "{sid} Status Codes",
"width": 12,
"x_offset": 0,
"y_offset": 9
},
{
"panel_key": "response_time",
"title_suffix": "{sid} Response Time",
"width": 12,
"x_offset": 12,
"y_offset": 9
},
{
"panel_key": "traffic_combined",
"title_suffix": "{sid} Traffic",
"width": 24,
"x_offset": 0,
"y_offset": 17
}
]
},
"panels": {
"row": {
"type": "row",
"collapsed": true,
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 0},
"panels": []
},
"rps": {
"type": "timeseries",
"description": "Requests per second",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 1},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {
"axisBorderShow": false,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "opacity",
"lineInterpolation": "smooth",
"lineWidth": 1,
"showPoints": "never",
"thresholdsStyle": {"mode": "line"}
},
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "dark-red", "value": 1800}
]
}
}
},
"options": {
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true},
"tooltip": {"mode": "single"}
},
"query_mode": "rps",
"query_config": {
"base_query": "SID:({sids})",
"metric": "count",
"alias": "{client_title} Total RPS",
"expression": "$A / 60",
"expression_ref_id": "RPS"
}
},
"status_codes": {
"type": "timeseries",
"description": "HTTP status codes",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 1},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {
"drawStyle": "line",
"fillOpacity": 10,
"lineInterpolation": "smooth",
"lineWidth": 1,
"showPoints": "never"
}
},
"overrides": [
{"matcher": {"id": "byName", "options": "2**"}, "properties": [{"id": "color", "value": {"fixedColor": "green", "mode": "fixed"}}]},
{"matcher": {"id": "byName", "options": "3**"}, "properties": [{"id": "color", "value": {"fixedColor": "blue", "mode": "fixed"}}]},
{"matcher": {"id": "byName", "options": "4**"}, "properties": [{"id": "color", "value": {"fixedColor": "yellow", "mode": "fixed"}}]},
{"matcher": {"id": "byName", "options": "5**"}, "properties": [{"id": "color", "value": {"fixedColor": "red", "mode": "fixed"}}]}
]
},
"options": {
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true},
"tooltip": {"mode": "single"}
},
"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"}
]
}
},
"response_time": {
"type": "timeseries",
"description": "Response time distribution",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 1},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {
"drawStyle": "line",
"fillOpacity": 30,
"lineWidth": 0,
"stacking": {"mode": "normal"}
},
"max": 100,
"min": 0,
"unit": "percent"
}
},
"options": {
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true},
"tooltip": {"mode": "single"}
},
"query_mode": "range_percent",
"query_config": {
"base_query": "SID:{sid}",
"field_filter": "upstream_response_time_digital:[0 TO *]",
"metric": "count",
"total_ref_id": "F",
"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"}
],
"range_field": "upstream_response_time_digital"
}
},
"traffic_combined": {
"type": "timeseries",
"description": "Response and Request Traffic",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 17},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {
"drawStyle": "line",
"fillOpacity": 10,
"lineInterpolation": "smooth",
"lineWidth": 1,
"showPoints": "never",
"spanNulls": 180000
},
"unit": "Kbits"
},
"overrides": [
{
"matcher": {"id": "byRegexp", "options": "/.*Response.*/"},
"properties": [
{"id": "color", "value": {"fixedColor": "green", "mode": "fixed"}}
]
},
{
"matcher": {"id": "byRegexp", "options": "/.*Request.*/"},
"properties": [
{"id": "color", "value": {"fixedColor": "blue", "mode": "fixed"}}
]
}
]
},
"options": {
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true},
"tooltip": {"mode": "single"}
},
"query_mode": "multi_sum_expression",
"query_config": {
"base_query": "SID:{sid}",
"targets": [
{
"alias": "Response Traffic",
"metric": "sum",
"field": "upstream_response_length_digital",
"ref_id": "A",
"expression": "$A / 60",
"expression_ref_id": "ResponseTraffic"
},
{
"alias": "Request Traffic",
"metric": "sum",
"field": "upstream_bytes_sent_digital",
"ref_id": "B",
"expression": "$B / 60",
"expression_ref_id": "RequestTraffic"
}
]
}
},
"traffic": {
"type": "timeseries",
"description": "Traffic",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 9},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {
"drawStyle": "line",
"fillOpacity": 10,
"lineInterpolation": "smooth",
"lineWidth": 1,
"showPoints": "never",
"spanNulls": 180000
},
"unit": "Kbits"
}
},
"query_mode": "sum_expression",
"query_config": {
"base_query": "SID:{sid}",
"metric": "sum",
"field": "upstream_response_length_digital",
"ref_id": "A",
"expression": "($A / 60)",
"expression_ref_id": "Traffic"
}
},
"status_codes_barchart": {
"type": "barchart",
"description": "Status codes distribution",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 9},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"}
}
},
"options": {
"legend": {"displayMode": "list", "placement": "bottom", "showLegend": true}
},
"query_mode": "bucket_logs",
"query_config": {
"base_query": "log_type:access AND SID:{sid}",
"metric": "count",
"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"
}
]
}
}
},
"queries": {
"bucket_agg": {
"field": "@timestamp",
"id": "2",
"settings": {"interval": "1m", "trimEdges": "1"},
"type": "date_histogram"
}
}
}