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

apps/bff/src/logic/layers/loader.ts:widgetsForScope() resolves 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

interface DashboardWidget {
  id: string;
  title: string;
  tip?: string;
  type: 'card' | 'line' | 'top' | 'record';
  expressions: string[];
  expressionLabels?: string[];      // tab labels for 'top'
  expressionUnits?: string[];       // per-expression unit override
  expressionAxes?: number[];        // 0 = left, 1 = right (dual y-axis)
  unit?: string;                    // widget-level unit suffix
  format?: 'int' | 'decimal' | 'compact';
  span?: number;                    // 12-col span; default 4
  rowSpan?: number;                 // row count; default 1
  visibleWhen?: string;             // visibility predicate
  layerScope?: boolean;             // evaluate against layer rather than selected service
  // legacy 24-col coordinates (back-compat with old templates):
  x?: number; y?: number; w?: number; h?: number;
}
Field Notes
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 /admin/layer-templates (verb dashboard:write). The editor shows the JSON tree with per-field type-aware controls. Changes are validated against the same schema as the bundled files, then written through POST /api/admin/layer-templates/:key.

Bundled templates remain in-place; admin edits override them per-instance and persist in the configured location.

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 (bundled or admin-edited) take effect on the next /api/menu or /api/layer/:key/dashboard/config request. Browsers see the new shape on the next page navigation. No BFF restart needed.

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.