KubeSense Dashboards
Build a dashboard preset — the same shape the Export button produces, so an exported
dashboard can be edited and re-imported.
Two ways to deliver it:
| MCP tool | Preset JSON |
|---|
| Applied by | the agent (write tool, needs approval) | the user, via Dashboards → Import |
| Use when | the user asked you to create one | they asked for the JSON, or want to review first |
| Shape to emit | the preset object itself | the import envelope, with stringified |
Both go through the same server-side schema check, so a preset that passes
is one that both paths accept.
When to Use
- "Create a dashboard for…" / "Build a dashboard with panels for…" →
- "Give me the dashboard JSON for…" → preset JSON
- "Generate a dashboard preset" → preset JSON
Always Validate Before You Finish
Call
on the preset before creating it or handing it over.
It stores nothing, so call it as often as you need.
# valid=false findings=2
path rule message
/panels/0/queries/0/selectedMode shape value must be one of 'logs', 'metrics', 'traces', 'formula'
/gridLayout/0 shape must have required property 'h'
Each
is a JSON Pointer to the exact value to fix. Repeat until
.
Pass the
preset object as
— not the import envelope with the stringified
field. Same for
's
argument.
Validation checks shape, not whether the data exists. A query against a metric nobody
collects is well-formed and will render empty, so discover names first.
Discover Fields First
If the KubeSense MCP server is connected, discover real names before writing queries —
never guess a metric, group-by, or filter field.
| To find | Tool |
|---|
| Metric names | |
| Metric labels (for ) | |
| Log/trace fields | |
Dashboard queries use storage-level field names, not the MCP catalog labels — the
webapp posts these directly. Take field names from the discovery output's storage column
where they differ, and tell the user to confirm on the panel preview. See
kubesense-mcp for the label/storage distinction.
Without MCP, fall back to user-provided names and say they need verifying.
Output Format
json
{
"name": "<dashboard name>",
"description": "",
"preset": "<stringified JSON of the preset object>"
}
[!IMPORTANT]
In this envelope, is a JSON string, not a nested object. The validator
declares
and only runs the preset schema after
.
Emitting an object fails with
"Preset must be a valid JSON string that matches the
dashboard preset schema".
This applies to the
import envelope only.
and
take the preset object itself — passing a stringified blob to
them works too, but do not wrap it in this envelope.
is required and non-empty.
is optional.
The object you stringify:
json
{
"gridLayout": [],
"panels": [],
"variables": [],
"subGrids": [],
"subGridLayout": []
}
Only
and
are required; the rest default to
. A sixth optional
key,
(string), also validates.
Minimal Valid Dashboard
Verified against the live validator:
json
{
"name": "Minimal",
"description": "",
"preset": "{\"gridLayout\":[{\"i\":\"0\",\"x\":0,\"y\":0,\"w\":12,\"h\":4}],\"panels\":[{\"name\":\"Up\",\"panelType\":\"timeSeries\",\"queries\":[{\"selectedMode\":\"metrics\",\"label\":\"A\",\"selectedMetric\":\"up\",\"functions\":[]}],\"config\":{}}],\"variables\":[],\"subGrids\":[],\"subGridLayout\":[]}"
}
What Hard-Fails Import
Get these wrong and import is rejected with an error:
- not a string.
- Missing or empty top-level .
- Missing or .
- A item missing any of , , , , , or with a wrong type
( instead of ).
- A panel missing , or .
- A panel missing the array (it may be , but the key must exist).
- A query whose is not | | | .
- A logs/traces query missing — the single hard-required field on
those queries.
- A entry with .
- A formula missing , or one containing lowercase letters or a decimal point.
- A formula referencing an undefined label, referencing itself, placed before the
queries it references, or with a multi-character label.
What Silently Destroys Your Data
More dangerous than hard failures — these validate cleanly and then discard what you sent.
Almost every field carries a
, so
a single bad value resets its whole array or
object to the default.
| Field | On a bad value |
|---|
| (metrics query) | The entire array is wiped to — one malformed entry loses every function |
| All variables deleted — one invalid variable empties the array |
| All sub-grids dropped |
| That row's panels emptied |
| Resets to {"function": "row_count"}
|
| Resets to |
| Resets to |
| Resets to |
| Resets to {"type":"palette","palette":"default"}
|
| Non-integer (e.g. ) resets to |
| resets the whole object to |
Because of this, prefer omitting an optional field over guessing its value — an
omitted field takes the default; a wrong one can take out its siblings.
[!WARNING]
must have at least as many entries as . Panels and layout are
matched
positionally by array index, not by the
value. A short
passes validation and then throws a TypeError when the dashboard renders. The
string
is only used to identify sub-grid rows (via a
prefix); real exports set it to the
index (
,
, …).
Panel
json
{
"name": "CPU by namespace",
"description": "",
"panelType": "timeSeries",
"queries": [],
"config": {}
}
— 12 values:
,
,
,
,
,
,
,
,
,
,
,
. Note camelCase
.
can never fail import (the whole object catches), so
is always safe and
inherits every default. For the full field list, defaults, and the
/
/
/
/
shapes, read
references/panel-config.md.
For a
table panel, per-column display names, visibility, cell type (
/
),
and threshold/range colouring go in
config.columnFormatting[]
— see the reference.
For a
top-list panel,
config.topListDisplayMode
is
(default) or
, and
needs
two or more entries — the first is the row, the rest are the
blocks stacked inside it. With one dimension the renderer draws flat whatever the field
says. Value-driven colouring goes in
config.visualFormattingRules[]
, which
supersedes
for this panel type. Both are in the reference.
For a
treemap panel, give the query a
and leave
alone — it has no
config of its own beyond
and the unit, and it draws one cell per returned
series, sized by value and coloured categorically from the panel palette. It is an instant
panel like
/
, so
does not apply. Its
is
(see below), since the query-level enum has no
arm. Every series comes back, so
cap the result with the query's own
rather than expecting the panel to trim it.
For an
alert panel, the query names a rule and the widget to draw it as:
is
,
or
.
and
need a
(and cache the rule's name in
);
also takes
,
(default) or
, which draws the rule's own query ranked by each series' last
value.
prints that rule's current figure alone — for a rule firing on several
series it is the one furthest past the threshold, highest for a
rule and
lowest for a
one.
reads
instead, plus
(
/
/
),
(
/
) and
(
/
/
).
decides what one row is:
one per rule,
one per firing instance (a rule grouped by
gets a
row per breaching pod),
one per rule with its firing instances counted beside
it. Unlike Datadog's equivalent,
cannot list healthy groups — the engine
materialises an instance only while it is firing, so a series that never breached has no
row to show.
Like slo it is served by the alert endpoints, not
, so
does not apply and its
is
. Datadog's fourth widget, Check
Status, has no arm: the engine persists only
and
, so a four-state
widget would have nothing to put in two of its cells.
Three config fields the old format got wrong:
- , not . Values: (default), ,
, , ,
filled_regions_and_lines_dashed
.
- , not . A 5-variant union; the palette variant is
{"type":"palette","palette":"default"}
and the only palette keys are ,
, , . To pin one query's colour regardless of the panel scheme,
use the query's own (see Per-query color).
- is and lives in the defaults — it is always
and cannot be disabled. Sending silently becomes . Just omit it.
Y-axis units
accepts
199 values — the same enum as
. Common
ones:
,
,
,
,
,
,
,
,
,
. Use
for any panel aggregating trace
—
the formatter auto-scales ns → µs/ms/s. Full list in
references/panel-config.md.
[!WARNING]
,
,
,
,
,
,
, and
do not exist and silently become
. Use
,
/
,
,
instead.
Queries
Discriminated on
:
|
|
|
. Note
is
not a valid
— use
with a logs/traces query and
.
Metrics query
json
{
"selectedMode": "metrics",
"label": "A",
"selectedMetric": "container_cpu_usage_seconds_total",
"functions": [],
"filters": {},
"queryMode": "builder",
"promql": "",
"visible": true,
"labelOptions": { "type": "auto" },
"pagination": { "page": 1, "page_size": 50 },
"fieldConfig": {}
}
Every field is optional.
is
or
— use
with
set,
with
+
.
is
auto-derived from filters and promql (
references) — anything you
supply is overwritten. Don't bother setting it.
Logs / traces query
json
{
"selectedMode": "logs",
"label": "A",
"columnFields": [],
"filters": {},
"queryMode": "builder",
"filterMode": "MFD",
"aggregation": { "function": "row_count" },
"groupBy": [],
"chart_type": "table",
"visible": true,
"sorting": {
"sortBy": { "field": "", "type": "string", "is_attribute": false },
"sortOrder": "ASC"
},
"query": "",
"pagination": { "page": 1, "page_size": 50 },
"fieldConfig": {}
}
[!IMPORTANT]
is required — include it on every logs/traces query, even as
.
Omitting it is a hard import failure. Entry shape:
{"field": "namespace", "type": "string", "is_attribute": false}
with optional
and
.
must be non-empty.
- — 7 values: , , , , , , .
Lowercase — silently becomes . This is a query-level
field, separate from the panel-level . There is deliberately no
arm: the panel is not offered for SPL/SQL, so a panel sets to
the way a panel sets .
- — , , , . Use with a
string for anything MFD's equality-only filters can't express (e.g. a latency threshold).
- entries use the same shape as .
- (array) and (string) are the SPL/SQL column fields. There is no
/.
json
{ "function": "p99", "fields": [ { "field": "duration", "type": "float", "is_attribute": false } ] }
- — no .
- — required, is or .
- , , , , , , , , — required and
must be literally .
Get any of that wrong — missing
,
on a numeric aggregation, or
(not a valid name) — and the whole aggregation silently resets to
, giving you a row count where you asked for a percentile.
Formula query
json
{ "selectedMode": "formula", "label": "C", "expression": "A/B", "visible": true, "fieldConfig": {} }
must match
after whitespace is stripped. Consequences:
- Uppercase only — fails.
- No decimal point — fails. Use .
- Labels are single letters A–Z; a two-character label breaks the dependency check.
- The formula must appear after the queries it references in the array.
- It cannot reference itself, or a label that doesn't exist.
Per-query color
Any query arm (metrics, logs, traces, formula) may carry
. It colours
every series that query yields and
outranks the panel , including the
stat panel's default base threshold. Only an explicit
of type
outranks it. Use it whenever the meaning of a query is fixed — errors red, success
green — so the colour does not depend on the query's position in the panel.
json
{ "selectedMode": "traces", "label": "A", "...": "...",
"fieldConfig": { "color": { "type": "single", "color": "#4AAD5A" } } }
{ "selectedMode": "traces", "label": "B", "...": "...",
"fieldConfig": { "color": { "type": "palette", "palette": "error" } } }
- — one hex colour (, , or with alpha). A grouped query that returns
several series is drawn in shades of it, not all identical.
- — | | | , cycled from the query's first
series. Optional (integer ≥ 0) starts at a later slot.
- Omit (or the whole ) to inherit the panel scheme.
A non-hex colour or an unknown palette name fails validation on import; the UI, if it
ever reads one, drops back to the panel scheme. Full shape in
references/panel-config.md.
Filters
json
{ "namespace": ["production"], "level": ["ERROR", "FATAL"] }
- Attribute keys are prefixed , e.g. .
- Exclusion is a prefix on the value:
{"namespace": ["-kube-system"]}
.
- A value is a dashboard-variable reference.
Metrics Query Functions
is an ordered pipeline.
One malformed entry wipes the entire array, and
argument counts are exact tuples — so build these carefully.
| type | names | arguments |
|---|
| , , | [{arg_name:"over", arg_value:"5m"}]
— any string |
| , , , , , | [{arg_name:"by", arg_value:["namespace"]}]
— must be an array |
| , | [{arg_name:"k",arg_value:5},{arg_name:"by",arg_value:"max"}]
— ∈ |
| , , , , , , , , , , | [{arg_name:"over", arg_value:"5m"}]
— restricted to |
| , , , , , | [{arg_name:"than"|"to", arg_value:100}]
|
| (+), (), (), (), (), / / (none) | arg names in parens; the key is still required — use for the zero-arg ones |
[!WARNING]
accepts any string, but
only accepts
, , , , , . A
with
silently deletes
every function on that query.
Typical time-series pipeline:
then
.
json
"functions": [
{ "type": "range", "name": "rate", "arguments": [ { "arg_name": "over", "arg_value": "5m" } ] },
{ "type": "aggregations", "name": "sum", "arguments": [ { "arg_name": "by", "arg_value": ["namespace"] } ] }
]
Grid Layout
json
{ "i": "0", "x": 0, "y": 0, "w": 6, "h": 4 }
All five keys required. 12-column grid,
100px, panel
2. No bounds are
validated —
is accepted and renders broken.
- : 0–11. Side by side: for two columns; for three.
- : increment by the previous row's height.
- : the array index as a string. Matching is positional, so keep in the
same order as , and at least as long.
Variables and Rows
Both are optional and most dashboards need neither — omit
,
, and
and they default to
.
If the dashboard needs a
template variable (a dropdown feeding
into query
filters) or
rows (collapsible panel groups), read
references/variables-and-rows.md for the schemas.
Two things to carry into that file: a variable's
is required (use
), and
one invalid variable silently deletes every variable.
Delivering It
Creating it directly
validate-dashboard-json → valid=true → create-dashboard
takes
,
(the object, or a JSON string containing it) and
an optional
. It returns the dashboard id and its UI path — quote that path so
the user can open it.
It is a write tool: only call it when the user has clearly asked for a dashboard to be
created, and say what you are about to create before calling. If it refuses, the response
names the JSON Pointer for every problem; fix them and retry rather than falling back to
handing over JSON.
Handing over JSON to import
- Copy the JSON to a file (the import UI requires ).
- KubeSense → Dashboards → Import.
- Upload and review, then save.
On this path never claim the dashboard was created — the user imports and confirms it.
Rules
Rule 0: run
before creating or handing over, and fix what it
reports. Everything in
What Hard-Fails Import is machine-checked
— the validator names the JSON Pointer, so it needs no checklist here.
This checklist is for what the validator is blind to. Every rule below passes
validation and then silently discards your data or crashes at render. A green validation
is not a working dashboard — check these by hand before you hand it over.
- must be at least as long as , in the same order — matching is
positional, and a short layout throws a TypeError when the dashboard renders.
- Numeric aggregations need exactly, or the whole aggregation
resets to — a row count where you asked for a percentile.
- is lowercase ; is camelCase . Each
resets to its own default ( / ) on a mismatch.
- only accepts /////; a bad value wipes every
function on that query.
- Variables need (use ) and a regex-valid ≤ 20 chars — one bad
variable deletes them all.
- Use real y-axis units (, , ) — , ,
become .
- Don't emit , , , , or
— none exist, and unknown keys are stripped without comment. On a top
list, value colouring goes in , not .
- Prefer omitting an optional field to guessing it: an omitted field takes its default, a
wrong one can reset its siblings.
- Validation checks shape, not existence. Discover metric and field names with MCP before
writing queries, and tell the user to confirm on the panel preview.
The one rule that spans both:
is a stringified JSON string in the import envelope,
but the plain object when passed to
or
.