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 memoryThe load averages are parsed out of show system uptime, the core count from
show hardware cpu, and the memory total from show system memory —
agent/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:
| Indicator | Means |
|---|---|
| telemetry live | The WebSocket at /api/stream is open and its last frame carried interface statistics. |
| polling | The socket is not connected. Every panel is still updating, from its own HTTP poll. |
| telemetry unavailable | The 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#
| Tile | Value | Source |
|---|---|---|
| Interfaces up | up/total, counting every configured interface except lo; the subtext lists their names | GET /api/interfaces/detail, polled every 5 s |
| Active sessions | Tracked conntrack flows; the subtext names the busiest source address | GET /api/sessions, polled every 5 s |
| Memory | Used, of total, with a meter | GET /api/system, polled every 10 s |
| Storage | Used, with free space and the filesystem, and a meter | GET /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().
Uplinks#
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 uplink — wan.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:
| Badge | Meaning |
|---|---|
carrying traffic | This uplink is the active default route. |
standby | Not active, but its route is in the forwarding table. |
no route | Not active and no route present. |
passing / failing / link down / no check / unknown | The 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:
| Left | Right |
|---|---|
| RX now — the newest sample's receive rate | TX now — the newest sample's transmit rate |
| RX total — the interface's lifetime byte counter | TX total |
| RX dropped | TX 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:
| Row | Counted from | Page it links to |
|---|---|---|
| NAT port forwards | Rules under nat destination in GET /api/nat/rules | NAT |
| Firewall rules | Every rule under both address families, in the input/forward/output base chains and in every named ruleset | Firewall |
| DHCP reservations | Every static-mapping under every subnet of every shared network | DHCP |
| Active DHCP leases | Rows in GET /api/dhcp/leases | DHCP |
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:
| Badge | Condition |
|---|---|
running | Podman reports the container Up, and either no health probe is defined or it passed. |
not answering | Running, but the health probe came back false. |
stopped | The config carries disable on this container. |
drift | The 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.
| Endpoint | Interval |
|---|---|
GET /api/interfaces/detail | 5 s |
GET /api/interfaces/history?points=90 | 5 s |
GET /api/sessions | 5 s |
GET /api/system | 10 s |
GET /api/dhcp/leases | 15 s |
GET /api/wan | 15 s |
GET /api/apps/installed | 20 s |
GET /api/nat/rules, /api/firewall/rules, /api/dhcp/reservations | on 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
/metricsor 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#
- Telemetry — the sampler, the stream, the cache and
/metrics. - QoS — what the qdisc badge on an interface panel means.
- Apps — installed — the full version of the apps panel.
- Diagnostics — health — when Active sessions looks wrong.
- System — overview — the long form of the identity panel.
- Interfaces — the full view behind each interface panel.
- Uplinks (multi-WAN) — the page the uplink strip links to.
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.