Overview Templates
An overview template is a JSON file describing a war-room / cross-cutting dashboard composed from MQE-driven widgets on a 12-column grid. Overviews are independent of any single layer and are designed for the operator’s “is everything OK?” pane.
Bundled templates: apps/bff/src/bundled_templates/overviews/<id>.json. Examples:
services.json— cross-layer service health + Kubernetes capacity summary.mesh.json— Istio data-plane services + pilot activity + Kubernetes.
Top-level shape
{
"id": "services",
"title": "Service Health",
"description": "Cross-layer service traffic, latency, errors, and capacity.",
"visibility": "public",
"icon": "services",
"order": 1,
"layers": ["GENERAL", "MESH", "K8S_SERVICE"],
"widgets": [
{ "type": "section-break", "title": "Service traffic", "cols": 12 },
{ ... metric widget ... },
{ ... kpi-tile ... },
{ "type": "section-break", "title": "Cluster capacity", "cols": 6 },
{ ... metric-composite ... }
]
}
Top-level fields
| Field | Type | Default | Notes |
|---|---|---|---|
id |
string | required | Stable id, used in the route /overview/:id. |
title |
string | required | Display title in the sidebar and page header. |
description |
string | — | One-line description shown under the title. |
visibility |
public | operate |
public |
Sidebar placement. operate puts the overview under the Operate group (admin-only by convention). |
icon |
string | — | Sidebar icon name (from Horizon’s icon set). |
order |
number | — | Sort order within the visibility bucket (lower = earlier). |
layers |
string[] | — | Layer enums this overview aggregates. Optional — Horizon also unions in every widget’s layer field, so a dashboard created via “+ New” (no layers[]) gates correctly off its widgets alone. See Sidebar visibility below. |
widgets |
array | required | Ordered widget list. The renderer iterates and lays out per the grid model. |
Widget types
Six supported type values:
| Type | Renders |
|---|---|
metric |
Single MQE scalar with optional unit. |
topology |
Service-map snapshot for the configured layer. |
section-break |
Visual row header; carries cols to override the grid column count for following widgets. |
kpi-tile |
Compound tile: optional service count + N KPI rows. |
alarms |
Active-alarm rail (60 min window). |
metric-composite |
Mixed KPI grid — number tiles + progress-bar rows. |
See Components → Overview Widgets for the per-widget detail.
Grid model
The overview renders on a CSS grid:
- Per-section column count, default 12, set by the most recent
section-break.cols. - Fixed row height 72 px.
- Per-widget
span(column width, 1–12) androwSpan(row height, 1–8). - Gap 12 px between widgets.
- Single-column responsive collapse below 1100 px viewport.
The 72 px row height is tuned for KPI tile content; widgets that need more vertical space (a small chart, a multi-row composite) use rowSpan: 2 or rowSpan: 3.
Widget shape (common fields)
| Field | Notes |
|---|---|
id |
Unique within the dashboard. |
title |
Card title (not used by section-break — uses title as the section header). |
tip |
Optional one-line hover hint next to the title. |
layer |
Layer key (UPPER_SNAKE). Used to scope MQE evaluation. Optional for section-break and alarms (alarms can scope server-side if the layer is set). |
type |
One of metric, topology, section-break, kpi-tile, alarms, or metric-composite. |
span |
Column span. Defaults vary per widget type. |
rowSpan |
Row span. Defaults vary per widget type. |
mqe, unit, aggregation |
Metric-specific fields. |
cols |
Section-break column count for following widgets. |
kpis, showCount, limit |
Type-specific fields described below. |
OverviewKpi
Used by kpi-tile and metric-composite:
| Field | Notes |
|---|---|
label |
Row label. |
mqe |
Required when source === 'mqe' (the default). |
unit |
Unit suffix. |
aggregation |
sum for throughput / count; avg for ratios and rates. |
style |
number (default) or progress-bar. |
max |
Required when style === 'progress-bar' — the 100% value. |
source |
mqe (default) or service-count — the latter reads the layer’s service count from the menu response instead of evaluating MQE. |
Worked examples
metric widget
{
"id": "total_rpm",
"title": "Total RPM",
"type": "metric",
"layer": "GENERAL",
"mqe": "sum(service_cpm)",
"unit": "rpm",
"aggregation": "sum",
"span": 3,
"rowSpan": 1
}
Single scalar tile. The MQE collapses to one number (here, sum over the time window).
kpi-tile with service count + two KPIs
{
"id": "general_summary",
"title": "General services",
"type": "kpi-tile",
"layer": "GENERAL",
"showCount": true,
"span": 4,
"rowSpan": 3,
"kpis": [
{
"label": "Apdex",
"mqe": "avg(service_apdex/10000)",
"aggregation": "avg",
"style": "progress-bar",
"max": 1
},
{
"label": "P95",
"mqe": "avg(service_percentile{p='95'})",
"unit": "ms",
"aggregation": "avg"
}
]
}
showCount: true adds a service-count header row above the KPIs.
metric-composite — mixed number + bar grid
{
"id": "k8s_summary",
"title": "Cluster capacity & utilisation",
"type": "metric-composite",
"layer": "K8S",
"span": 12,
"rowSpan": 3,
"kpis": [
{ "label": "Nodes", "mqe": "latest(k8s_cluster_node_total)", "aggregation": "avg" },
{ "label": "Pods", "mqe": "latest(k8s_cluster_pod_total)", "aggregation": "avg" },
{ "label": "CPU",
"mqe": "k8s_cluster_cpu_cores_requests / k8s_cluster_cpu_cores * 100",
"unit": "%", "aggregation": "avg",
"style": "progress-bar", "max": 100 },
{ "label": "Memory",
"mqe": "k8s_cluster_memory_requests / k8s_cluster_memory * 100",
"unit": "%", "aggregation": "avg",
"style": "progress-bar", "max": 100 }
]
}
The widget auto-splits KPIs:
number-style KPIs (Nodes, Pods) go into the count-tile row (auto-fit, min 100 px).progress-bar-style orunit === '%'KPIs go into the bar grid (auto-fit, min 180 px).
This single widget replaces what used to be three separate hand-crafted widgets (k8s-service-count, pilot, service-count) — anything compound now goes through metric-composite.
section-break to start a new row
{ "type": "section-break", "title": "Cluster capacity", "cols": 6 }
Following widgets render in a 6-column grid (rather than 12) until the next section-break. Used for paired side-by-side panes.
alarms rail
{
"id": "active_alarms",
"title": "Active alarms (60 min)",
"type": "alarms",
"layer": "GENERAL",
"limit": 10,
"span": 4,
"rowSpan": 4
}
Read-only — Horizon does not support acknowledge / close / silence operations. Alarm recovery is backend-automatic.
Sidebar visibility
An overview entry appears in the sidebar only when at least one of its declared layers is currently reporting services. Declared layers come from two sources, unioned:
- the explicit
layers[]field on the dashboard, and - every
widget.layerreferenced by its widgets.
A dashboard with no layer reference on either side (no layers[] and no widgets with layer set — e.g. a future cross-layer “All” overview) is always shown.
This makes the sidebar honest: it stops listing a Services dashboard when nothing is reporting and lights it back up automatically when an agent / receiver does start, on the same 60-second cadence the menu refreshes. It also means a dashboard you create via “+ New” — which has no layers[] — gates correctly off its widgets without you having to maintain a separate list.
Admin Editor
Overview templates are editable at runtime via Dashboard setup → Overview templates (/admin/overview-templates, verb overview:write). Pick a dashboard from the filterable dropdown (title + id + sync status), then lay it out on a 12-column canvas: drag a widget to reorder, corner-drag to resize, click a widget to edit it in the right-hand drawer. Section breaks (“text widget” / line break) and the dashboard title are selectable too. The canvas shows sample data so you can judge layout; only the live page (Preview ▾) uses real OAP data.
Per-widget fields (the drawer shows only what the widget.type needs):
| Type | Fields shown |
|---|---|
section-break |
title, cols |
metric |
layer, title, tip, mqe, unit, aggregation, span, rowSpan |
topology |
layer, title, tip, span, rowSpan |
alarms |
layer, title, tip, limit, span, rowSpan |
kpi-tile |
layer, title, tip, showCount, KPI rows (add / remove), span, rowSpan |
metric-composite |
layer, title, tip, KPI rows (each a stacked card: label / source / MQE / unit / aggr / style / max), span, rowSpan |
How edits flow: draft → preview → publish
Same model as layer templates: your edit lives in your browser, and the live page everyone sees stays on the published OAP version until you publish.
- Save (local). Stores your draft in this browser only; the dashboard is tagged local in the picker.
- Reset to ▾ loads the Bundled or Remote version into the canvas.
- Preview ▾ opens the real overview page in a new tab rendering Local / Bundled / Remote.
- Check diff & push shows a remote → local diff and publishes to OAP (create-or-update). Enabled only when your draft differs from remote.
A + New dashboard form (inside the picker) creates a dashboard the same way: it writes a local draft (id is the template name, must be unique) — edit and preview it, then Check diff & push publishes it to OAP. A pushed dashboard with no bundled default is remote-only; the picker, the live page, and the sidebar all show it.
A top banner summarizes state — Synced from OAP — N diverged, Y local — with Diverged / Local filters. Status chips per row: synced, diverged (OAP wins at render), remote-only, disabled (deleted), bundled.
Deleting a dashboard
OAP has no hard delete, so the Delete button next to the title soft-disables the dashboard on OAP (a disabled dashboard drops from the picker’s live state, the sidebar, and the live page). A dashboard that exists only as an unpublished local draft is removed from your browser instead. Either action is confirmed in a dialog first; deletion is irreversible from the UI, but a new dashboard can always be created with the same id.
Hot reload
Admin editor changes apply on the next overview refresh. Bundled file changes made outside Horizon require a BFF restart.
Common patterns
A war-room single-screen view
One overview, id: war-room, with layers: [ALL_YOUR_LAYERS], and:
section-break“Health” + 4 ×kpi-tile(one per critical layer with service-count + RPS / error-rate KPIs).section-break“Alarms” + 1 ×alarmswidget (span 12) showing every firing alarm.section-break“Capacity” + 1 ×metric-composite(span 12) with cluster capacity.
Layout fits a 1440 px display above the fold; works on a wall projector at 1920 px.
A team-specific overview
visibility: operate, only granted to the team’s role via landingByRole:
landingByRole:
payments-on-call: /overview/payments
The team lands directly on their own overview after login.
Replacing the old k8s / pilot / service-count widgets
Use metric-composite with one widget per cluster summary. The old per-feature widget types are no longer rendered — metric-composite is the unified shape.