Docs Dashboards

Dashboards

Dashboards let you build your own monitoring pages from ready-made widgets:
mock traffic counters, test-case statistics, fuzzing findings, chaos experiment status,
recent audit activity and more. Each dashboard is a free-form 12-column grid — drag,
resize and pin widgets until the page shows exactly what your team needs.

Custom dashboard with several widgets

About URLs in examples: all examples use localhost:5770 as the default Mockarty address. If your instance runs on a remote server, replace localhost:5770 with its actual address (e.g. https://mockarty.company.com). See Tips & Useful Features for details.

Where to find it

Open Dashboards in the sidebar, or go to /ui/widget-dashboards.
Clicking a dashboard opens it at /ui/widget-dashboards/<id>. The legacy
/ui/dashboards URL redirects here.

An empty namespace offers a one-click Starter dashboard — a ready-made
overview (traffic, protocols, test-run error rate, top mocks, recent activity)
you can then tune to your team.

Dashboards are namespace-scoped: each dashboard belongs to one namespace, and the
namespace switcher at the top of the page changes which dashboards you see.

While a namespace or dashboard is loading, its previous cards are hidden. If the
list cannot be loaded, the page shows Failed to load dashboards with a Retry
button. This is a loading error; it does not mean your dashboards were deleted.
If the Add widget catalog fails to load, its picker stays open and offers
Retry there as well.

On first open, a new namespace receives editable starter dashboards, including
Deployments. It shows deployment runs by current state for the last 30 days,
the number of runs that need attention, available runners, and webhook delivery
status. The attention count includes older unresolved runs and rollbacks that
have not been verified yet. Deployment data sources are available only on
namespace dashboards; shared dashboards cannot
aggregate deployment records across namespaces.

The starter set also includes Deploys — an operator view of the durable
deploy ledger written by the autonomous-coder deploy pipeline: outcomes for the
period (succeeded / failed / rolled back), p50 and p95 deploy duration, and the
latest deploys with their per-run outcome. Like the Deployments board, the
deploy sources are namespace-only.

The deploy ledger records deploys performed by the autonomous coder. Without
that module in your licence the Deploys widgets are hidden from the picker,
and the seeded board is created without them.

Who can do what

Action Who
View dashboards and widget data Any member of the namespace
Create / edit / delete dashboards and widgets Namespace owner, or a global admin / support user
Create / edit / delete shared (global) dashboards System admin / support only

A user with the viewer role in a namespace can open and read every dashboard in it,
but any attempt to change something is refused with 403.

Creating a dashboard

  1. Open /ui/widget-dashboards and click New dashboard.
  2. Enter a name (required, up to 200 characters, unique within the namespace) and an
    optional description.
  3. The empty dashboard opens in edit mode — add your first widget.

You can also duplicate an existing dashboard: the copy gets a new name you choose
and carries over all widgets with their settings and layout.

Shared (global) dashboards

A regular dashboard shows the data of one namespace. A shared dashboard
aggregates data across every namespace of the instance — the company-wide
view: total mock traffic, test cases by priority, fuzzing findings by severity,
the whole runner fleet.

How it works:

  • A shared dashboard lives in the default namespace (sandbox) — the one every
    user can open — so everyone in the company sees it there. Its tab carries a
    globe icon, and the toolbar shows the All namespaces badge.
  • Only a system admin or support user can create or modify a shared
    dashboard. For everyone else it is read-only: the edit controls are hidden,
    and direct API writes answer 403.
  • To create one, open New dashboard in the default namespace and tick
    Shared dashboard (across all namespaces) — the checkbox is visible to
    admins and support only. Via the API, pass "scope": "global" in the create
    body; outside the default namespace the request is refused with 400.
  • Because every user can see a shared dashboard, only privacy-safe data
    sources
    are allowed on it — aggregates that carry no resource names, ids,
    e-mail addresses, namespace names or free-form labels. The widget picker
    hides incompatible sources, and the API refuses them with 400. The catalog
    marks each source with a globalSafe flag. An existing shared widget that
    uses such a source shows an unavailable-data error until you replace it.
    A formula on a shared dashboard can use only operands that are also allowed
    on shared dashboards; older formulas with incompatible operands show an
    unavailable-data error.

Existing dashboards are not affected: everything created before stays
namespace-scoped.

# Create a shared dashboard (admin/support token, default namespace)
curl -X POST -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Company overview", "scope": "global"}' \
  "http://localhost:5770/api/v1/widget-dashboards?namespace=sandbox"

Adding widgets

Click Add widget and pick a data source from the catalog. Each widget combines:

Add widget — data source picker

  • a data source — what to show (fixed once the widget is created);
  • a widget type — how to render it;
  • optional parameters — e.g. the time window or the number of entries.

Widget types

Type Renders as
stat Big number
chart_line Line chart (time series)
chart_area Filled area chart (time series)
chart_bar Bar chart (categorical comparison)
chart_pie Donut / pie of a breakdown
top_list Ranked top-N list
table Table
activity Activity feed (recent events)
status Health indicator with short text
breakdown Number with sub-bucket roll-up
text Your own Markdown note (with Mermaid diagrams)

Data sources by category

Category Source Shows Parameters
Mocks mock.total_count Total number of mocks in the namespace —
Mocks mock.active_count Number of currently active mocks —
Mocks mock.undefined_count Requests that matched no mock period_days (0 = all time, 1–365)
Mocks mock.requests_total Total mock requests over a window period_days
Mocks mock.top Most-hit mocks period_days, top_n
Mocks mock.unused Active mocks nobody called for N days (value = days idle) idle_days (1–365), top_n (1–50)
Mocks mock.distinct_called How many distinct mocks got at least one request in the window period_days (1–365)
Mocks mock.by_tag Active mocks grouped by tag (untagged bucket included) top_n (1–50)
Mocks mock.requests_trend Requests over time period_days (1–90), bucket (hour / day)
Mocks mock.requests_by_protocol Traffic split by protocol period_days
Audit audit.activity Recent audit events (who did what) limit (1–100)
Audit audit.action_breakdown Activity split by action type period_days (1–365), top_n (1–50)
Runners runner.status Runner fleet by status (online / offline) —
Test Cases tcm.summary Test cases by priority —
Test Cases tcm.cases_by_status Test cases by workflow status folder_id, priority, review_status, severity (all optional)
Test Cases tcm.cases_by_review_status Test cases by review status (draft / in review / approved …) folder_id, priority, severity (optional)
Test Cases tcm.cases_by_priority Test cases by priority folder_id, review_status, severity (optional)
Test Cases tcm.cases_by_severity Test cases by severity folder_id, priority, review_status (optional)
Test Cases tcm.cases_in_review How many cases are currently in review folder_id, priority, severity (optional)
Test Cases tcm.review_age_days Average / max age in days of cases in review (proxy, see below) folder_id, priority, severity (optional)
Test Cases tcm.cases_by_tag Test cases grouped by tag (a case counts in every tag it carries; untagged cases bucket into “Untagged”) top_n (1–50), folder_id, priority, review_status, severity
Test Cases tcm.cases_by_custom_field Test cases grouped by the value of one custom field — the Allure-style “group by label” chart. Field names match regardless of case; if one case has duplicate names with different casing, its last value is counted. field (required), top_n (1–50), folder_id, priority, review_status, severity
Test Cases tcm.cases_by_author Top authors of test cases (per team) top_n (1–50), folder_id, priority, review_status, severity
Test Cases tcm.runs_by_executor Top people who ran cases in the window (per team) top_n (1–50), period_days (1–365)
Test Plans plans.summary Plans, schedules and recent runs —
Test Plans testplan.completions_by_user Top people who completed test plans in the window (per team) top_n (1–50), period_days (1–365)
Access users.cases_authored Per-user case-authoring activity in the window (per team) top_n (1–50), period_days (1–365)
API Tester api_tester.summary Collections, tests and reports counts —
API Tester api_tester.runs_trend Test-report runs over time (feeds error-rate charts & formulas) period_days (1–90), bucket (hour / day), status (all/passed/failed)
Fuzzing fuzz.summary Fuzzing findings by severity —
Chaos chaos.summary Chaos experiments by status —
Chaos chaos.resilience_avg Average resilience score (0–100) across chaos runs in the window period_days
AI Agents agent.tokens_total LLM tokens consumed over a window period_days (1–365)
AI Agents agent.tokens_by_namespace Token consumption split by namespace period_days, top_n (1–50)
AI Agents agent.tokens_trend LLM tokens over time period_days (1–90), bucket (hour / day)
AI Agents agent.users_top Top agent users by tokens (per team) period_days, top_n
AI Agents agent.subagents_top Top sub-agents by completed tasks period_days, top_n
Autonomous Coder coder.mission_cost LLM tokens per coder mission (top consumers) period_days (1–365), limit (1–50)
Autonomous Coder coder.hours_saved Human-hours saved by missions completed in the period (issue estimate − runtime to completion) period_days (1–365), hours_per_point (1–80)
Missions missions.by_status Autonomous missions grouped by state period_days (1–365), product_id (optional)
Missions missions.needs_attention Missions waiting for a person product_id (optional)
Missions missions.throughput Finished missions per completion day, including failed and canceled missions period_days (1–180), product_id (optional)
Missions missions.success_rate Successful share of completed missions; canceled missions are excluded period_days (1–365), product_id (optional)
Missions missions.spend_by_product Mission token use by product period_days (1–365), limit (1–50)
Missions missions.budget_utilisation Share of the configured mission token budget used product_id (optional)
AI Agents agent.tasks_active Agent tasks currently running —
AI Agents agent.tasks_by_status Agent tasks split by status period_days
Mocks mock.users_top Top mock authors (per team) period_days, top_n
Security security.findings_total Security findings over a window period_days (1–365)
Security security.findings_by_severity Findings split by severity period_days
Security security.findings_trend Findings over time period_days (1–90), bucket, severity (all / critical / high / medium / low / info)
Performance perf.campaigns_active Load campaigns currently running —
Performance perf.campaigns_by_status Load campaigns split by status period_days
Performance perf.requests_trend Total perf requests executed over time period_days, bucket
Performance perf.failed_requests_trend Failed perf requests over time (pair with requests for an error-rate formula) period_days, bucket
Performance perf.apdex_avg Average APDEX score across runs in the window period_days
Performance perf.p95_latency_trend Average p95 latency (ms) per bucket over time period_days, bucket
Test Runs test_runs.by_status Test runs split by status period_days
Test Runs test_runs.trend Test runs over time period_days (1–90), bucket, status (all / completed / failed / running / pending / interrupted)
Webhooks webhook.deliveries_by_status Webhook deliveries split by status period_days
Webhooks webhook.deliveries_trend Webhook deliveries over time period_days (1–90), bucket, status (all / delivered / failed / dlq / …)
Recorder recorder.sessions_total Recorder sessions over a window period_days
Deploys deploy.outcomes Deploy outcomes for the period: succeeded / failed / rolled back / blocked / cancelled / in flight period_days (1–90), environment (optional)
Deploys deploy.durations Deploy duration percentiles (seconds); the stat value shows the chosen percentile period_days (1–90), percentile (p50 / p95), environment (optional)
Deploys deploy.runs Latest deploys with their per-run state and duration limit (1–50), period_days (1–90), environment (optional)
Contracts contract.summary Contract runs split by report type period_days
Access rbac.namespace_users Namespace members split by role —
Tasks issuetracker.issues_by_status Issues split by workflow status —
Tasks issuetracker.issues_by_priority Issues split by priority —
Tasks issuetracker.issues_by_assignee Issues split by assignee (per team) —
Tasks issuetracker.open_count Open (not done) issues —
Tasks issuetracker.overdue_count Issues past their due date —
Tasks issuetracker.created_trend Issues created over time period_days (1–90), bucket (hour / day)
Messenger chat.messages_by_author_kind Chat messages split by author kind (people / agents / system) —
Messenger chat.messages_by_thread_kind Chat messages split by thread kind (discussions / DMs / channels) —
Messenger chat.threads_total Discussion threads (open, not archived) —
Wiki wiki.pages_total Live wiki pages —
Wiki wiki.pages_created_trend Wiki pages created over time period_days (1–90), bucket (hour / day)
Wiki wiki.edits_trend Wiki edits (saves) over time period_days (1–90), bucket (hour / day)
System system.resources Host CPU / memory / disk utilisation —
System system.runtime Server process runtime (goroutines, heap, GC, uptime) —
System system.db_pool Database pool connections, wait count and wait time on the node serving the dashboard (not a cluster total) —
Formulas formula.custom Your own derived metric computed from other sources expression, operands
Formulas formula.timeseries Your own chart: a formula evaluated per time bucket over trend sources (e.g. error rate over time) expression, operands
Other static.text Your own Markdown note (text widget) —
Other static.html Your own HTML (scripts allowed) rendered in a sandboxed frame —
Other static.embed A live external page framed as a card (e.g. a Grafana board) —

Hourly and daily buckets for test runs, security findings, AI token use, API
Tester reports, webhook deliveries, performance requests and latency, and
mission throughput use UTC. A chart therefore shows the same bucket on each
server node even when their database sessions use different time zones.

Parameters are validated against the source’s schema when you save — an unknown
parameter or an out-of-range value is rejected with a clear error message, the widget
is not created.

The exact, always-current catalog (including each source’s parameter schema) is
available from the API: GET /api/v1/widget-dashboards/catalog.

Team analytics

Beyond “how is the tool being used”, dashboards answer management questions about
people and process
: who is writing the most test cases, who completed which test
plans, how many cases are sitting in review and for how long.

  • Process sources (tcm.cases_by_review_status, tcm.cases_by_priority,
    tcm.cases_by_severity, tcm.cases_in_review, tcm.review_age_days) show
    the shape of the test-case backlog with fixed labels or counts. They can be
    shown on a shared global dashboard. Workflow status names, tags and
    custom-field values can contain team data, so their breakdowns stay scoped
    to one namespace.
  • People sources (tcm.cases_by_author, tcm.runs_by_executor,
    testplan.completions_by_user, users.cases_authored) attribute work to the
    individual teammate. Because they surface a person’s identity, they are
    team-scoped only: you see your own team’s contributors, never people in
    another team, and they cannot be added to a global dashboard.

All process and people sources accept optional filters so you can narrow to one
folder, priority, review status or severity — pick a value when adding the widget,
and the widget recomputes against only the matching cases. Two indirect-signal
filters narrow by the same metadata Allure users rely on: tag (only cases carrying
that tag) and cf (only cases with a custom field, written as key=value, e.g.
component=cart). Combine them — e.g. “cases by priority, tag=regression,
cf=team=payments” — to slice any breakdown the way your process actually works.

Review age is a proxy. tcm.review_age_days reports how long cases have been in
review by measuring the time since each case was last edited (now − last update). A
case that has been actively edited shows a younger age; a stale case in review shows
its true backlog age. The widget labels this honestly as a proxy — Mockarty does not
yet store a dedicated “moved into review at” timestamp.

Formulas — your own metrics

The Custom formula source (category Formulas) lets you build a metric Mockarty
does not ship out of the box — by combining other sources with plain arithmetic. No
query language: you name up to 5 operands (letters A–E), bind each to a data
source, and write an expression like A / B * 100.

In the Add-widget picker, choose Custom formula and the configuration step shows
a formula builder instead of the usual parameter form:

Add widget — formula builder

  • Operands — one row per letter. Each row picks a data source (any source that
    produces a number or a distribution; a formula cannot reference another formula).
    For distribution sources you also choose the metric: Sum (total) or Item by
    name…
    — one named slice, matched case-insensitively (e.g. failed). If the
    source takes parameters (like period_days), a compact parameter form appears
    right in the row.
  • Expression — arithmetic over the operand letters: + - * /, parentheses,
    numbers (decimals are fine), unary minus. Validated live as you type; the same
    validation runs on the server when you save.
  • Display — decimals (Auto / 0–3) and a free-text suffix (e.g. %) for the
    rendered number.

The result is a single number, so the Stat and Status widget types apply —
including thresholds (e.g. turn the card red when the failure rate crosses 5).

Example — a “failed test runs %” stat:

  1. Operand A: source Test runs by status, metric Item by name… → failed.
  2. Operand B: source Test runs by status, metric Sum (total).
  3. Expression: A / B * 100, suffix %, decimals 1.

Division by zero and a missing item name render as an error inside that widget only —
the rest of the dashboard keeps working.

Add a formula widget via the API

curl -X POST -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "title": "Failed runs %",
    "widgetType": "stat",
    "dataSourceId": "formula.custom",
    "paramsJson": {
      "expression": "A / B * 100",
      "operands": [
        {"ref": "A", "sourceId": "test_runs.by_status", "select": "item:failed"},
        {"ref": "B", "sourceId": "test_runs.by_status", "select": "total"}
      ]
    },
    "vizOptionsJson": {"decimals": 1, "suffix": "%"},
    "gridW": 3, "gridH": 3
  }' \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/widgets?namespace=default"

select is "total" (default) or "item:<name>" for distribution sources; number
sources need no select. An invalid expression, an unknown operand source, or a
letter used in the expression without an operand row is rejected with a precise
error message.

Custom charts — a formula over time

The Custom chart (formula over time) source (formula.timeseries) is the same
builder, but every operand binds to a trend source ("… over time") and the
expression is evaluated once per time bucket — the result is a series you render
with the Line or Area widget types.

The builder starts with Quick recipes — one-click templates for the common
formulas (test-run error rate, webhook success share, critical-findings share).
Pick a recipe and adjust, or build from scratch:

  1. Set the Time axis — one window (days) and bucket (hour / day) for the
    whole formula. Every operand uses this axis, so the series always align.
  2. Operand A: source Test runs over time, parameter status → failed.
  3. Operand B: source Test runs over time, status → all.
  4. Expression: A / B * 100. Widget type: Line.

A bucket where the expression cannot be computed (for example, division by zero
on a day with no runs) is simply left out — the chart shows a gap instead of a
fake zero.

Display options work here too: set decimals and a unit suffix (e.g. %) and
the chart’s y-axis and tooltip show 60.7 % instead of a raw number. The tooltip
names the series after the widget title.

Threshold zones. Line, area and bar charts accept the same thresholds as the
stat widgets — and draw them as colored zones: a translucent band starts at each
threshold value with a dashed boundary line, so “above 5% is bad” is visible at a
glance. The y-axis extends to keep the highest zone in view even when the data has
not reached it yet. Add thresholds in the widget’s settings or via the API
(vizOptionsJson.thresholds, e.g. [{"value": 5, "color": "yellow"}, {"value": 10, "color": "red"}]).

Text & notes — Markdown widgets

The Text / note source (category Other) adds a free-form text card to the
dashboard: a runbook excerpt, on-call contacts, a legend explaining the metrics
around it, links to related docs. It is the only widget that fetches no data — you
write the content yourself.

Pick Text / note in the Add-widget picker and the configuration step shows a
Content (Markdown) editor instead of parameters. The content supports standard
Markdown:

  • headings, paragraphs, bold / italic;
  • bullet and numbered lists, tables, quotes;
  • inline code and fenced code blocks;
  • links and images.

Raw HTML is sanitized away — scripts, frames and event handlers never render, so a
text widget is safe to put on a shared (global) dashboard too.

Custom HTML & live embeds

Two more author-your-own widgets live next to Text / note (category Other):

Custom HTML (static.html) renders HTML you write — scripts included — inside
a sandboxed iframe on the card. Scripts can call the application’s own API with
your session (fetch('/api/v1/…', {credentials: 'include'})), pull data from
anywhere else you can reach, and draw the result however you like. This makes the
dashboard fully programmable: an agent (or you) can generate a bespoke
visualisation as a single HTML snippet and drop it onto the board. The card
resizes like any other widget and the size is saved. Content limit: 16 KB.
Because the HTML runs with the viewer’s session, authoring is restricted: only a
namespace owner (or a system admin / support) can create or edit a Custom HTML
widget — the server enforces it. This mirrors Grafana’s admin-only HTML panels.

Embed / iframe (static.embed) frames a live external page inside the card:
a Grafana board, a status page, any dashboard reachable from the viewer’s browser.
Enter the page URL — the target must allow embedding (it must not send
X-Frame-Options: DENY / a restrictive frame-ancestors). Authentication is the
browser’s own: sign in to the embedded site in the same browser and its cookies
apply inside the frame (for cross-site setups the target must issue its session
cookie with SameSite=None; Secure). Grafana tip: append ?kiosk to hide its
chrome, and enable allow_embedding = true in grafana.ini.

Mermaid diagrams

A fenced code block with the mermaid language renders as a diagram right on the
card — flowcharts, sequence diagrams, state machines:

# Release flow

```mermaid
graph LR
  Build --> Test --> Deploy
```

Diagrams follow the light/dark theme automatically. If a diagram has a syntax
error, an inline error note appears in its place — the rest of the card still
renders.

To change the text later, open the widget’s settings (gear icon) — the same editor
appears with the current content.

Add a text widget via the API

The content travels in vizOptionsJson.markdown (up to 16 KB); paramsJson stays
empty for this source:

curl -X POST -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "title": "Team runbook",
    "widgetType": "text",
    "dataSourceId": "static.text",
    "vizOptionsJson": {"markdown": "# On-call\n\n- Check the error-rate widget first\n\n```mermaid\ngraph LR\n  Alert --> Triage --> Fix\n```"},
    "gridW": 4, "gridH": 4
  }' \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/widgets?namespace=default"

Layout: drag, resize, pin

The grid has 12 columns. In edit mode:

  • Drag a widget by its header to move it; neighbouring widgets are pushed out of
    the way automatically.
  • Resize from the bottom-right corner. Each widget has a minimum size.
  • Pin a widget to protect it from being pushed around by other widgets.

The layout is saved automatically after a drag/resize session and survives page
refreshes for everyone in the namespace.

Live updates

The dashboard you are viewing updates itself — there is no need to press
Refresh. A small dot next to the Refresh button shows the connection state:

  • Green, pulsing — live updates are active. The dashboard checks for new
    widget values and repaints when they change.
  • Grey — the connection dropped and the page is reconnecting; it recovers
    on its own within a few seconds.

Test-case changes also refresh the related dashboard totals when they are
made through another Mockarty admin node. A short delay while the change
reaches the dashboard is normal.

While you are editing the layout, live repaints are held back so the grid does
not move under your cursor; the freshest data appears as soon as you save or
cancel the edit.

The auto-refresh interval select in the toolbar remains available as a
polling fallback (for example, when a corporate proxy blocks streaming
connections). With live updates working, leaving it Off is the right
choice.

Exporting & printing

Open the dashboard’s ⋮ menu (top-right of the toolbar) to share a dashboard
outside Mockarty:

  • Export HTML — downloads a single, fully self-contained .html file: the
    dashboard exactly as you see it (grid layout, cards, charts as static images),
    with no external resources, so it opens anywhere — including air-gapped
    machines and e-mail attachments. The file also carries machine-readable
    attributes (data-widget-id, data-widget-type, data-source-id,
    data-params, data-grid on every card; data-value on stat numbers), so
    scripts and BI tooling can parse the snapshot without scraping the visuals.
  • Export JSON — downloads the dashboard configuration plus freshly computed
    widget data from the server: {dashboard, widgets, data}. Each widget entry
    carries its type, dataSourceId, params, grid position and
    thresholds; data carries the same per-widget slots as the live render.
  • Export CSV — downloads a flat metric table for Excel / Google Sheets / BI
    tools: widget_id, widget_title, widget_type, data_source_id, metric, value,
    one row per value (a pie chart yields one row per slice, a time series one
    row per point). The file starts with a UTF-8 BOM so Excel detects the
    encoding correctly.
  • Export configuration — downloads the dashboard’s configuration only (no
    data, no internal ids, no namespace) as a portable JSON file. Use it to hand a
    dashboard to another team — see the next section.
  • Import dashboard — creates a new dashboard from such a configuration file.
    Also available from the empty state when the namespace has no dashboards yet.
  • Print / PDF — opens the browser print dialog. Pick Save as PDF as the
    destination. App chrome (sidebar, tabs, toolbar) is hidden automatically; the
    widget grid prints in a light palette, and each card stays on one page.

JSON and CSV exports are computed server-side, so they reflect the current data,
not what your browser cached. Every export is recorded in the audit log.

Sharing a dashboard between teams

When one team builds a dashboard worth copying, export its configuration and
hand the file over — the receiving team imports it into their own namespace and
the widgets recompute against their data:

  1. On the source dashboard, open the ⋮ menu → Export configuration. You
    get a <name>-config.json file containing only the dashboard name,
    description and widget configuration (type, data source, parameters,
    thresholds, grid layout). Nothing namespace-specific travels with it.
  2. The receiving team opens Custom Dashboards in their namespace and picks
    ⋮ menu → Import dashboard (or Import dashboard on the empty state),
    then selects the file. A new dashboard appears and becomes active.

Import rules:

  • Importing requires write access (namespace owner, admin or support).
  • The file is validated strictly: widget types and data sources must exist on
    the receiving server, and widget parameters are re-checked against each data
    source’s schema. Unknown data sources are listed in the error message.
  • If a dashboard with the same name already exists, the import is named
    <name> (imported) (then <name> (imported 2) and so on).
  • The 30-widget and 200-character limits apply to imports too.

Limits

  • 30 widgets per dashboard — the 31st add is refused, including when several people add widgets at the same time.
  • If an older dashboard already contains more than 30 widgets, dashboard, data and export requests return HTTP 409. Ask an administrator to repair the stored configuration; the server does not silently omit widgets.
  • Dashboard names and widget titles: up to 200 characters.
  • Widget data is cached server-side for a short, per-source interval (from a few
    seconds up to a few minutes), so a busy dashboard stays cheap even with many viewers.
  • If one data source fails, only that widget shows an error — the rest of the
    dashboard renders normally.

API examples

All endpoints live under /api/v1/widget-dashboards. The namespace travels in the
?namespace= query parameter (defaults to default). Authenticate with an API token
in the X-API-Key header.

List dashboards

curl -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards?namespace=default"

Create a dashboard

curl -X POST -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Team QA overview", "description": "Mocks + test health"}' \
  "http://localhost:5770/api/v1/widget-dashboards?namespace=default"

Browse the data-source catalog

curl -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/catalog"

Add a widget

curl -X POST -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "title": "Requests over time",
    "widgetType": "chart_line",
    "dataSourceId": "mock.requests_trend",
    "paramsJson": {"period_days": 7, "bucket": "day"},
    "gridX": 0, "gridY": 0, "gridW": 6, "gridH": 4
  }' \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/widgets?namespace=default"

Get a dashboard with its widgets

curl -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>?namespace=default"

Fetch live widget data

curl -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/data?namespace=default"

Each item in the response carries either data (the rendered value) or error
(why this particular widget could not compute) — a failing source never breaks the
whole response.

When this node is busy rendering other dashboards, /data returns HTTP 429
with Retry-After: 1. Retry after that interval rather than sending many
concurrent requests. The limit applies independently on each cluster node.

Stream live widget data (SSE)

curl -N -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/stream?namespace=default"

The server keeps the connection open and pushes an event: widgets frame with
the same items shape as the /data endpoint — but only when the values
actually change. Comment heartbeats keep the connection alive in between. The
web UI uses this stream for its live updates; you can consume it from scripts
or external dashboards as well.
When rendering capacity is busy, the stream keeps its last frame and retries
on the next update interval; an unchanged frame is not sent again.

Export a dashboard (JSON / CSV)

# Configuration + freshly computed data as JSON
curl -OJ -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/export?namespace=default&format=json"

# Flat metric table for Excel / BI tools
curl -OJ -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/export?namespace=default&format=csv"

-OJ saves the file under the name the server suggests
(<dashboard-name>-<date>.json / .csv).
JSON and CSV exports may return HTTP 429 with Retry-After: 1 while data
rendering is busy. A configuration-only export does not compute widget data and
remains available.

Move a dashboard to another team (config export + import)

# 1. Export the portable configuration (no data, no ids, no namespace)
curl -OJ -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/export?namespace=team-a&format=config"

# 2. Import it into another namespace (file from step 1 as the body)
curl -X POST -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  --data @overview-config.json \
  "http://localhost:5770/api/v1/widget-dashboards/import?namespace=team-b"

The import answers 201 with the created dashboard. A 400 lists exactly what
the receiving server did not accept (e.g. unknown data sources).

Update dashboard name / description

curl -X PATCH -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Team QA overview v2"}' \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>?namespace=default"

Update a widget

curl -X PATCH -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{"title": "Mock traffic (14d)", "paramsJson": {"period_days": 14}}' \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/widgets/<widgetId>?namespace=default"

Save the layout in bulk

curl -X PUT -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{"items": [{"id": "<widgetId>", "gridX": 6, "gridY": 0, "gridW": 6, "gridH": 4}]}' \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/layout?namespace=default"

Duplicate a dashboard

curl -X POST -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Team QA overview (copy)"}' \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/duplicate?namespace=default"

Delete a widget / a dashboard

curl -X DELETE -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>/widgets/<widgetId>?namespace=default"

curl -X DELETE -H "X-API-Key: $TOKEN" \
  "http://localhost:5770/api/v1/widget-dashboards/<dashboardId>?namespace=default"

After a dashboard is deleted, reads on it answer 404.