Skip to content

Admin — Dashboard

The Dashboard is the operator’s day-one landing page. It’s designed to answer one question: “Is the tenant healthy right now?” — not to replace the drill-in pages for threads, agents, or analytics.

Route: /dashboard (_authed/_tenant/dashboard)

The layout is simple: five metric cards across the top, then two paginated lists (Recent Activity, Recent Threads) in a two-column grid on larger screens.

CardWhat it shows
Agents OnlineCount of agents currently in IDLE or BUSY status
Open ThreadsCount of threads not in DONE or CANCELLED
Recent ActivityCount of runs and thread updates in the last 24 hours
Spend (MTD)Month-to-date spend from the cost store
Cost / EventAverage cost per turn event over the period

Cost metrics come from the same useCostStore (Zustand) that the Analytics page uses, so the dashboard does not re-fetch cost data on every navigation — it reads from the already-hydrated store.

A unified feed of recent runs and thread updates, merged by timestamp and paginated client-side at 10 items per page. Each row shows:

  • An icon for run vs thread update
  • The agent name (for runs) or thread title (for thread updates)
  • A status badge (running, succeeded, failed for runs; thread status colors for thread updates)
  • Relative timestamp

Clicking a run row opens the RunDetailDialog — a modal showing the run’s execution details and the final response markdown. Clicking a thread row opens the ThreadDetailDialog — a modal showing thread metadata, description, and recent comments.

A second paginated list, 10 per page, showing the tenant’s most recently-touched threads. This is intentionally narrower than the full Threads page: it exists so the operator can quickly glance at what’s been happening without leaving the dashboard.

The dashboard fires four GraphQL queries on mount:

  • AgentsListQuery — populates the Agents Online metric
  • ThreadsListQuery — powers Recent Threads and half of the activity feed
  • ThreadTurnsQuery — powers the run side of the activity feed
  • ThreadDetailQuery — lazy-loaded when a thread is clicked into the drill-in dialog

Cost metrics come from the useCostStore hook which internally fetches /api/cost (a REST endpoint) the first time it’s used in the session.

Both lists paginate client-side using a custom Pager component. Pagination state is per-list — flipping to page 2 of activity does not affect threads pagination.

Refreshes are push-driven, not polled:

  • Any thread-turn event fires OnThreadTurnUpdatedSubscription, which re-executes ThreadTurnsQuery and the activity merge rebuilds
  • Any thread change fires OnThreadUpdatedSubscription, which re-executes ThreadsListQuery and Recent Threads rebuilds

The dashboard is always within a few seconds of reality. Operators do not need to pull to refresh.

  • No dedicated activity-feed query. Client-side merge of runs and threads is the current implementation. A real feed query would let the dashboard show more than just the intersection of runs + thread updates.
  • Cost metrics can be undefined on first render. The useCostStore hydrates asynchronously; components render ?? 0 placeholders until it lands. This flashes zeros for a tick on cold load.
  • No per-metric drill-in. Clicking “Agents Online” does not navigate anywhere. The drill-in for agents is Agents; for threads it’s Threads; for spend it’s Analytics.