Wheelhouse docs

Dashboard#

The Dashboard is the page at /, the first thing a signed-in browser sees. It answers one question — is this router doing what it should be doing right now — from eight endpoints polled at different rates, and it is deliberately built so that a number it could not read shows as an em dash rather than as a zero. A confident 0 on a landing page is a false all-clear, and this product does not ship one.

Every panel below names the endpoint behind it. If a panel is empty or dashed, the Telemetry page explains how to find out which read failed.

The header#

The subtitle line is assembled from a single GET /api/system response:

Load 0.14 / 0.09 / 0.03 over 1, 5 and 15 minutes · 3 interfaces · 4 cores · 3.84 GB memory

The load averages are parsed out of show system uptime, the core count from show hardware cpu, and the memory total from show system memoryagent/opmode.go, handleSystem. The interface count is GET /api/interfaces/detail minus lo.

At the right sits the telemetry indicator, and it has three states, not two:

IndicatorMeans
telemetry liveThe WebSocket at /api/stream is open and its last frame carried interface statistics.
pollingThe socket is not connected. Every panel is still updating, from its own HTTP poll.
telemetry unavailableThe socket is open, but the agent's last frame was an error frame — it could not read the router. Hovering shows the router's own words.

That third state exists because a silent stream and a working one look identical from the browser. The agent sends an explicit error frame rather than nothing, so a router that has stopped answering shows as a problem instead of as an idle link — agent/main.go, handleStream.

The four stat tiles#

TileValueSource
Interfaces upup/total, counting every configured interface except lo; the subtext lists their namesGET /api/interfaces/detail, polled every 5 s
Active sessionsTracked conntrack flows; the subtext names the busiest source addressGET /api/sessions, polled every 5 s
MemoryUsed, of total, with a meterGET /api/system, polled every 10 s
StorageUsed, with free space and the filesystem, and a meterGET /api/system

The memory meter is a percentage the browser computes, and it computes it in bytes. VyOS prints each size in whatever unit suited that number, so 3.84 GB sits next to 512M in the same response; comparing the numbers in front of the suffixes gives a nonsense percentage. humanSize() normalises both to bytes first — ui/src/lib/format.ts.

The storage meter is different: it reads the percentage the router already printed in its own storage line (765M (9%)) rather than recomputing it, through percentIn().

A strip of one row per uplink, showing the interface, its gateway, whether it is carrying traffic, and its health verdict. It appears only when the router has more than one uplinkwan.uplinks.length > 1. A single-WAN router gets nothing here, because a strip reading "1/1" is noise, and the interface panel below already covers it.

Data comes from GET /api/wan, polled every 15 s. The small grey label beside the title is the multi-WAN mechanism in use; when that mechanism is static distances the label says so plainly — "ranked by distance, no health checks" — because there is nothing probing anything in that mode.

The health badge repeats VyOS' verdict rather than a measurement of the agent's own:

BadgeMeaning
carrying trafficThis uplink is the active default route.
standbyNot active, but its route is in the forwarding table.
no routeNot active and no route present.
passing / failing / link down / no check / unknownThe health verdict for that uplink, with unmonitored rendered as no check.

The per-interface panels#

One panel per interface (again excluding lo), each with a two-series throughput chart and six counters. The panel title carries the interface name, an up/down badge, and — when the kernel has something other than noqueue attached — the qdisc name, which is the fastest way to see that a shaper is actually installed.

The chart is ThroughputChart from ui/src/components/Sparkline.tsx: receive and transmit on one axis, because they share a unit and a second scale would be a lie. It draws the last 90 samples from GET /api/interfaces/history?points=90 and labels the window from the sampler's own interval, so the caption reads "peak 41.2 Mbps · last 450s" rather than assuming a rate. Below 2 samples it says Collecting samples… instead of drawing a line through one point.

The numbers under the chart:

LeftRight
RX now — the newest sample's receive rateTX now — the newest sample's transmit rate
RX total — the interface's lifetime byte counterTX total
RX droppedTX errors

RX now and TX now are derived by the agent, not by the browser. That matters when you open a fresh tab: the agent has been sampling since it started, so the chart is already populated instead of drawing its first line a minute later. How that works is on the Telemetry page.

Configured objects#

Four counts, each a link to the page that owns them:

RowCounted fromPage it links to
NAT port forwardsRules under nat destination in GET /api/nat/rulesNAT
Firewall rulesEvery rule under both address families, in the input/forward/output base chains and in every named rulesetFirewall
DHCP reservationsEvery static-mapping under every subnet of every shared networkDHCP
Active DHCP leasesRows in GET /api/dhcp/leasesDHCP

The firewall count walks firewall.ipv4 and firewall.ipv6; a count that only looked at one family reported a dual-stack router as half configured. The reservation count has to descend shared-network-name → subnet → static-mapping, because that is where VyOS puts them — both are small functions at the foot of ui/src/pages/Dashboard.tsx.

These four refresh when the staging revision changes — that is, after you commit — not on a timer. They describe configuration, and configuration does not move on its own.

Top talkers#

Up to six source addresses from GET /api/sessions, sorted by bytes and then by flow count, each with its flow count and total bytes. The aggregation happens in the agent: it reads show conntrack table ipv4, sums original plus reply bytes per originating host, and sorts — agent/opmode.go, handleSessionsFixed.

When conntrack cannot be read the panel says so in amber and names the error. When it can be read and is empty, it says "No tracked connections right now." — which is a different statement from a failure, and the page keeps them apart.

Installed apps#

Up to seven installed apps from GET /api/apps/installed, polled every 20 s, each linking to its own app page. The badge is the merge of three separate facts:

BadgeCondition
runningPodman reports the container Up, and either no health probe is defined or it passed.
not answeringRunning, but the health probe came back false.
stoppedThe config carries disable on this container.
driftThe config declares it, it is not disabled, and it is not running.

The panel meta reads 2/3 running. If the container status could not be read at all the panel says "Container status could not be read" and the meta reads unknown.

This router#

Identity, from GET /api/system plus the product constants in ui/src/components/product.ts: product name and version, host name, build date, architecture, hardware model, CPU model, core count, and the running boot image.

This panel deliberately shows the product's identity rather than dumping the base platform's show version keys. The base is credited by name on the About page, which is where attribution belongs and where it will not be mistaken for the thing the customer is running. resolveProduct() prefers a product block from the agent when one appears and falls back to the router's own version keys until then.

Poll rates, in one table#

Every one of these is an HTTP request from the browser; several of them cost the router a forked shell. If you are watching load on a small box, this is the list.

EndpointInterval
GET /api/interfaces/detail5 s
GET /api/interfaces/history?points=905 s
GET /api/sessions5 s
GET /api/system10 s
GET /api/dhcp/leases15 s
GET /api/wan15 s
GET /api/apps/installed20 s
GET /api/nat/rules, /api/firewall/rules, /api/dhcp/reservationson commit only
GET /api/stream (WebSocket)a frame every 2 s

Most of those are served from the agent's read cache rather than from the router. The Telemetry page explains which ones bypass it, and why the two that do have to.

What this page will not do#

  • It does not keep history. The chart window is the sampler's in-memory ring — 240 samples at 5 s, about 20 minutes — and it is not persisted. Restart the agent and the charts start again from empty. For retention, scrape /metrics or export flows.
  • There is no per-host traffic history and no time-series database in the product. Top talkers is a live aggregation of the current conntrack table, not a record.
  • The tiles are not configurable. There is no widget layout, no per-user dashboard and no per-app dashboard widget.

See also#


Checked against ui/src/pages/Dashboard.tsx, ui/src/components/Sparkline.tsx, ui/src/components/product.ts, ui/src/lib/format.ts, ui/src/lib/hooks.ts, agent/opmode.go, agent/main.go, agent/metrics.go, agent/wan.go.

Updated 2026-09-02 manual dashboard monitoring