Layer Dashboard Templates

A layer template is a single JSON file that describes everything Horizon needs to know about one OAP layer: its display name, color, sidebar grouping, which sub-tabs to expose, the service-list picker columns, the per-scope widget grids, the trace/log/topology routing, and the service-name parsing rule.

There is one template per layer, stored under apps/bff/src/bundled_templates/layers/<key>.json (lowercase filename matches the OAP layer enum, e.g. general.json for the GENERAL layer).

Top-level shape

{
  "key": "GENERAL",
  "alias": "General Service",
  "group": "Application",
  "visibility": "public",
  "color": "var(--sw-accent)",
  "documentLink": "https://skywalking.apache.org/docs/main/next/en/concepts-and-designs/scopes/",
  "slots": { ... },
  "components": { ... },
  "header": { ... },
  "overview": { ... },
  "dashboards": {
    "service":   [ ... widgets ... ],
    "instance":  [ ... widgets ... ],
    "endpoint":  [ ... widgets ... ],
    "topology":  [ ... widgets ... ],
    "trace":     [ ... widgets ... ],
    "logs":      [ ... widgets ... ],
    "traceProfiling":  [ ... widgets ... ],
    "ebpfProfiling":   [ ... widgets ... ],
    "asyncProfiling":  [ ... widgets ... ]
  },
  "topology": { ... },
  "endpointDependency": { ... },
  "traces": { "source": "native" },
  "log": { ... },
  "naming": { ... }
}

Every field is optional except key. Defaults are baked in for the rest.

Top-level fields

Field Type Default Notes
key string (UPPER_SNAKE) required Matches the OAP layer enum. The filename is the lowercased key.
alias string OAP-reported name Display name in the sidebar and page headers.
group string Sidebar grouping label. Layers sharing a group collapse together.
visibility public | operate public Section placement. operate puts the layer under the Operate group.
color string var(--sw-accent) Hex or CSS variable for the layer’s accent.
documentLink string (URL) External docs URL; renders as a small chip on the layer page.
slots object OAP defaults Per-layer entity term overrides (see below).
components object all-true Which sub-tabs are enabled (see below).
header object Service-list picker columns + default sort.
overview object Overview tile config (groups of self-contained metrics) shown above the dashboard.
dashboards object Per-scope widget arrays (the bulk of the template).
topology object Topology MQE override for the service-map view.
endpointDependency object API-dependency dashboard MQE override.
traces { source?: 'native' | 'zipkin' | 'both' } native Trace backend selection for this layer.
log object Logs tab scope (service / instance / endpoint).
naming object Service-name parsing rule (extracts cluster or other tokens from the OAP-reported name).

slots

Layer-specific term overrides used in UI labels.

"slots": {
  "service":      "service",
  "services":     "services",
  "instance":     "instance",
  "instances":    "instances",
  "endpoint":     "endpoint",
  "endpoints":    "endpoints"
}

A Kubernetes layer might use pod / pods instead of instance / instances. The page titles and pickers pick up the override automatically.

components

Per-tab feature toggles. A false value hides the tab.

"components": {
  "service":   true,
  "instance":  true,
  "endpoint":  true,
  "topology":  true,
  "trace":     true,
  "logs":      false,
  "profiling": true
}

The landing tab when a layer is clicked is the first enabled in the priority order service → instance → endpoint → topology → trace → logs → profiling.

The service-list picker on the layer landing page. Columns sortable, with one designated default sort.

"header": {
  "orderBy": "cpm",
  "columns": [
    {
      "metric": "cpm",
      "label": "RPM",
      "mqe": "service_cpm",
      "aggregation": "sum"
    },
    {
      "metric": "apdex",
      "label": "Apdex",
      "mqe": "service_apdex/10000",
      "aggregation": "avg"
    },
    {
      "metric": "p95",
      "label": "P95",
      "mqe": "service_percentile{p='95'}",
      "unit": "ms",
      "aggregation": "avg"
    }
  ]
}
Field Type Notes
orderBy string metric value of the column that should sort by default.
columns[].metric string Unique id for the column (referenced by orderBy).
columns[].label string Column header label.
columns[].mqe string MQE expression evaluated per service.
columns[].unit string Optional unit suffix.
columns[].aggregation sum | avg Aggregation across the time window.

overview

Header summary tiles on the layer page (above the dashboard grid). Renders self-contained, sub-layout-aware groups of metrics.

"overview": {
  "groups": [
    {
      "title": "Latency & errors",
      "size": "auto",
      "metrics": [
        {
          "id": "p95",
          "label": "P95",
          "mqe": "service_percentile{p='95'}",
          "unit": "ms",
          "aggregation": "avg"
        },
        {
          "id": "errors",
          "label": "Errors",
          "mqe": "service_resp_time_percent_99",
          "unit": "%",
          "aggregation": "avg"
        }
      ]
    }
  ]
}
Field Type Notes
groups[].title string Group header.
groups[].size auto | wide Layout hint. wide doubles the group’s column allocation.
groups[].metrics[].id string Unique id within the group.
groups[].metrics[].label string Tile label.
groups[].metrics[].mqe string MQE expression evaluated layer-wide (or per-service if a service is selected).
groups[].metrics[].unit string Unit suffix.
groups[].metrics[].aggregation sum | avg Aggregation across the time window.

dashboards

The bulk of the template. A map from scope to an ordered widget array.

"dashboards": {
  "service": [
    { "id": "rpm", "type": "line", "title": "RPM", ... },
    { "id": "p95", "type": "line", "title": "P95 latency", ... },
    { "id": "errors", "type": "card", "title": "Error rate", ... },
    { "id": "top_apis", "type": "top",  "title": "Top 20 APIs", ... }
  ],
  "instance": [ ... ],
  "endpoint": [ ... ]
}

Scope enum

Scope Page
service Service drill-down (primary). Used as fallback when other scopes are unset.
instance Single service instance.
endpoint Single endpoint.
dependency Endpoint-to-endpoint relationships.
topology Service-map visualization.
trace Trace explorer.
logs Log viewer.
traceProfiling SkyWalking trace-driven profiling.
ebpfProfiling eBPF profiling.
asyncProfiling JVM async-profiler.

Scope resolution

Widgets for a scope resolve in this order:

dashboards[scope] → dashboards.service → template.widgets (legacy)

A layer without an explicit instance widget set will reuse service widgets on the instance page. The fallback keeps minimal templates short.

Dashboard widget fields

Field Notes
id Unique widget id within the dashboard.
title Widget title shown in the card header.
tip Optional hover hint.
type Widget kind, usually card, line, top, record, or table.
expressions[] MQE expressions to run.
expressionLabels[] Tab labels for top, legend labels for line.
expressionUnits[] Per-expression unit override.
expressionAxes[] 0 for left axis, 1 for right axis on dual-axis line charts.
unit Widget-level unit suffix.
format int, decimal, or compact.
span 12-column width. Default 4.
rowSpan Row count. Default 1.
visibleWhen Visibility predicate.
layerScope Evaluate against the whole layer rather than the selected service.
x, y, w, h Legacy coordinates kept for old templates. Prefer span and rowSpan.
type card for single scalar (MQE collapses to one number); line for time-series; top for sorted list; record for tabular records (slow SQL, slow statements).
expressions[] Array of MQE expressions. card typically uses one; line uses one per series; top may use multiple (each becomes a tab).
expressionLabels[] Used by top to label each tab.
expressionUnits[] Per-expression unit when expressions have heterogeneous units (e.g. ms + count).
expressionAxes[] Two-axis charting. 0 = left y-axis (default), 1 = right.
unit Widget-level unit (used when all expressions share the same unit).
format Numeric formatting: int, decimal, compact (K / M suffixes).
span Column span in the 12-col grid. Default 4 = three widgets per row.
rowSpan Vertical span. Default 1 (one 120 px row).
visibleWhen Predicate. Two supported shapes: #entity.<key> (truthy if the named entity key is set; e.g. #entity.serviceInstance to show only when an instance is selected) and <metric> has value (only show if the metric returns data).
layerScope If true, MQE evaluates against the whole layer rather than the selected service. Used for layer-level summaries on the service page.

Choosing type

The widget type must match the MQE shape:

  • Outermost call latest(...), max(...), min(...), avg(<plain-metric>), sum(<plain-metric>) → collapses to one scalar → type: card.
  • Outermost call relabels(...), top_n(...), histogram*(...), rate(...), increase(...), aggregate_labels(...) without scalar collapse → series → type: line.
  • Outermost call top_n(...) returning a labeled list → type: top.
  • Database-shaped record returns → type: record.

A line widget with a scalar-collapsed MQE renders a one-point chart and confuses operators. The widget editor warns; the schema does not enforce.

topology

Per-layer override for the service-map view’s MQE.

"topology": {
  "metric": "service_resp_time"
}

Without an override, topology uses a default metric appropriate to the layer.

endpointDependency

Per-layer override for the API-dependency dashboard.

"endpointDependency": {
  "metric": "endpoint_avg"
}

traces

"traces": { "source": "native" }
Source Behavior
native (default) Traces queried via OAP’s native trace query.
zipkin Traces queried via the Zipkin v2 endpoint at oap.zipkinUrl.
both Both sources, with a UI toggle.

log

"log": { "scope": "service" }
Scope Behavior
service Logs are queried per service.
instance Logs are queried per service instance.
endpoint Logs are queried per endpoint.

naming

Service-name parsing rule. Extracts a cluster (or other token) from the OAP-reported service name so the UI can show a grouped picker.

"naming": {
  "pattern": "^([^|]+)\\|(.+)$",
  "groups": { "cluster": 1, "name": 2 }
}

When set, the layer’s service list groups by cluster. Without it, services are listed flat.

Admin Editor

Layer templates are editable at runtime via Dashboard setup → Layer dashboards (/admin/layer-dashboards, verb dashboard:write). The editor shows layer-specific controls for service, instance, endpoint, topology, trace, log, and profiling views.

The save/publish model has two steps:

  1. Save locally. “Save locally” writes your edit to the local bundled copy and renders it immediately for preview — it does not touch OAP. The template now shows as diverged (local differs from what OAP serves), the row carries a Synced from OAP — N diverged banner, and the affected layers show a yellow warning icon in the sidebar. Save works even when OAP is unreachable.
  2. Publish. Sync all to OAP pushes the diverged templates to OAP (the runtime source of truth) — only the ones that differ — behind a confirmation that lists exactly what will be written. After publishing, the template is synced and everyone sees it.

A Diverged only filter and a Showing: Local / Remote display toggle sit at the top of the page; Show diff opens a side-by-side bundled-vs-OAP comparison.

Local vs. remote conflicts

OAP is the source of truth at runtime, so by default the app renders the OAP-stored version. When your local edits diverge, a per-session prompt (listing the affected items by menu name) asks which to render:

  • Keep my local edits — render your local copy for preview; publish later with Sync all.
  • Use live — overwrite your local copy with the remote (OAP) version. This discards your local edits and is confirmed first; use it when OAP holds the newer version.

Bundled examples

File Layer Notes
general.json GENERAL Reference shape — service/instance/endpoint dashboards, top_apis, header columns.
mesh.json MESH Istio data-plane. Uses mesh_ metric family.
k8s.json K8S Kubernetes cluster. Slots use pod instead of instance.
mesh_cp.json MESH_CP Istio control-plane (Pilot).
various One per OAP layer.

Read the bundled JSON for the closest layer to yours before authoring a new template — most of the work is renaming MQE expressions to match your layer’s metric prefix.

Hot reload

Template changes made in the admin editor take effect on the next menu or dashboard refresh. Bundled file changes made outside Horizon require a BFF restart.

Common patterns

Borrow from another layer

Templates are not inheritance-aware. To “inherit” from general.json, copy it and rename MQE expressions. There is no extends: keyword.

Hide a tab entirely

"components": { "logs": false }

The Logs tab disappears from the layer page nav. Existing direct-URL navigation to /layer/<key>/logs redirects to the first enabled tab.

Add a layer-wide summary widget on the service page

{
  "id": "layer_total_rpm",
  "type": "card",
  "title": "Layer-wide RPM",
  "expressions": ["sum(service_cpm)"],
  "layerScope": true,
  "span": 3
}

layerScope: true evaluates the MQE against the entire layer rather than the selected service.