The fleet directory is the robotics entry screen in the ClutchCall console. It renders one row per registered robot, a four-tile KPI strip, and an optional map view. Two procedures back the screen: Rows are rendered as the BFF returns them. The screen does not synthesise robots, and it does not hide a robot that has stopped reporting telemetry — a registered robot with no live session still occupies a row.

The status model: ok, warn, bad, offline

Every row carries a status of ok, warn, bad, or offline. The status is computed by the BFF and delivered on the row; the console only renders it (as the status dot, as the map pin colour, and as a filter value). Two consequences of that table are worth internalising:
  • bad is still online. A robot in error is connected and talking, which is why the “Robots online” tile can read 12 / 12 while the hint underneath says 3 errored.
  • offline is the only status excluded from the online count. The console’s fallback online count is ok + warn + bad, and its fallback errored count is the number of bad rows. These fallbacks are used only when kpiStrip does not return online_robots / errored_robots.
The Status filter chip cycles through Any → ok → warn → bad → offline. It is an exact match on the row’s status, not a severity threshold, so filtering to warn does not include bad.

Where each row field comes from

pose.x / pose.y are not shown in the list. They are used only by the map view, which drops a pin per robot with the row’s status as the pin colour and the robot id as the label. Clicking a row navigates to that robot’s detail screen at /robot/<id>.

Missing values and the degraded-telemetry flag

Telemetry fields can be absent while the robot record still exists. The screen distinguishes “unknown” from a real zero rather than collapsing both to 0. Per row:
  • fps and rtt render as an em dash (—) when the field is null or absent. A reported 0 fps renders as 0.0 in grey — that is a robot publishing nothing on its video track, which is a different condition from no telemetry at all.
  • A row with no pose.x / pose.y is filtered out of the map view. It still appears in the list. The map’s “N robots” hint counts the rows passing your filters, not the number of pins drawn, so a fleet with partial pose data shows fewer pins than that number.
  • The Battery filter reads a missing battery as 0. A robot with no battery reading therefore survives the < 20% filter and is excluded by ≥ 20% and ≥ 50%.
Fleet-wide: when kpiStrip returns source: "degraded", there is no live session to aggregate. The console then blanks the three session-derived tiles rather than showing zeros: Total robots and active alerts are not session-derived, so they keep their values under the degraded flag. If every tile but “Robots” is dashed out, the fleet is registered but nothing is streaming; that is not an error in the console.

Filters, search, and pagination

robotsList is paginated server-side at 50 rows per page. Search and the filter chips are then applied in the browser, to the rows of the current page. The counter in the filter bar reads X of Y, where Y is the row count delivered for that page.
  • Search matches a case-insensitive substring against id, name, model, task, and site joined together.
  • Site and Model chips are built from the distinct non-empty values present in the loaded rows, so their option lists reflect the current page.
  • Status cycles the four statuses. Battery cycles Any → ≥ 20% → ≥ 50% → < 20%.
  • Tag filter narrows further, and reports how many rows matched out of the rows that survived the other filters.
Changing search or any chip resets to page 1. Export writes the currently filtered rows to fleet-robots.csv with the columns id, name, model, site, status, battery, task, fps, rtt, alerts, last_seen. It exports what you can see — the filtered current page — not the whole fleet. The button is disabled when no rows match.

Bulk actions and their current status

Select rows with the checkboxes to raise the bulk bar. Only one action is wired up in the current build: Emergency stop asks for confirmation first, naming the number of robots and the topic it will publish to. It then attempts each robot independently and reports the ones it could not reach in an alert listing their ids. A failure for one robot does not abort the others, and the list is not rolled back — treat the reported ids as robots that are still moving and re-issue the stop for them.

How a registered robot becomes live

A row exists because the robot is registered, not because it is connected. The lifecycle you see in this screen is:
  1. Registered. Add the robot with Add robot, or let it register with the relay. Until robotsList returns any rows, the table shows Robots appear here once they register with the relay — add one to begin. (If rows exist but your filters exclude all of them, you get the different message No robots match the current filters — clear them to see the whole fleet.)
  2. Registered, not reporting. The row shows identity fields — name, id, model, site, tags — with status: offline, a non-live lastSeen, and dashes for fps and rtt. With no session anywhere in the fleet, kpiStrip reports source: "degraded" and the online, fps, and RTT tiles dash out.
  3. Live. Once the robot publishes telemetry, lastSeen becomes the literal live and renders as the blinking indicator, status moves to ok, warn, or bad, and battery, task, fps, rtt, alerts, and pose populate. The robot now counts toward the online tile and, with a pose, appears as a pin on the map.
Because both queries poll every 30 s, a robot that has just come up can take until the next poll to flip from offline to live. Use the refresh button in the filter bar to refetch both the rows and the KPI strip immediately; the timestamp beside it shows when the row data last arrived.
  • Telemetry — the metric, trace, and CDR streams behind these numbers
  • Authentication — API keys and relay tokens used by control-plane calls