Agora
Dashboard

Dashboard

Mount the Telescope console — the @adonis-agora/telescope-ui React SPA served under the same prefix and auth guard as the core JSON API + SSE it reads from the headless TelescopeService — browse entries by type, family and trace, and reach the same data programmatically.

The Telescope console is the @adonis-agora/telescope-ui React SPA (pre-built) for browsing recorded entries. It's served under the same prefix and behind the same auth guard as the core @adonis-agora/telescope/ui subpath it reads: the core mounts the JSON API and SSE stream directly onto your app's router — no separate process, no build step of your own — all served from the headless TelescopeService; the SPA package mounts the page on top of it.

Mount it

The core JSON API + SSE is the @adonis-agora/telescope/ui subpath of the one telescope package — re-run configure and pick UI:

npm i @adonis-agora/telescope
node ace configure @adonis-agora/telescope   # pick "UI"

configure registers @adonis-agora/telescope/ui_provider, publishes config/telescope_ui.ts, and mounts the API + SSE routes under the configured prefix against the same live store the watchers write to. It does not install a page — that's the separate @adonis-agora/telescope-ui package, and the core configure prompt above doesn't add it for you:

npm i @adonis-agora/telescope-ui
adonisrc.ts
providers: [
  // …
  () => import('@adonis-agora/telescope/ui_provider'),
  () => import('@adonis-agora/telescope-ui/telescope_ui_dashboard_provider'),
]

Register @adonis-agora/telescope-ui/telescope_ui_dashboard_provider after the core ui_provider. Only once both are registered does http://localhost:3333/telescope serve a page — with just the core installed, that prefix only exposes <path>/api/*.

The UI needs @adonis-agora/telescope to be enabled and booted — it reads the same store the core publishes. If the store isn't booted, the provider registers no routes (and warn-logs why) rather than crashing the host. So the dashboard simply won't exist until the core is running.

What it shows

The console is a client of the JSON API below and nothing else. Its left sidebar carries ten sections:

  • Overview — the triage landing page: error rate, failed jobs, slow routes, recent failures, N+1 hotspots, load by user, and the current retention posture.
  • Entries — every captured entry, newest-first, filterable by type and free-text search, with a Live tail toggle that streams new ones in over SSE. Opening a row shows the full type-specific content — the SQL and bindings for a query, the stack for an exception, the payload for a diagnostic.
  • Traces — every entry recorded under one trace id, so you can replay a whole request's story: the request, its queries, the diagnostics it published, and the exception that ended it, laid out as a waterfall with any N+1 loops flagged.
  • Pulse — the health rollup over a selectable window.
  • Exceptions — exceptions grouped by class and message, with occurrence counts and a when-it-fired sparkline.
  • Queues and Schedules — the live consoles for jobs and scheduled tasks.
  • Exports — download a filtered slice of the entries feed as JSON or CSV.
  • Profiles — captured CPU flamegraphs.
  • Extensions — dashboards contributed by installed extensions.

A Watchers sub-nav below them jumps straight to Entries filtered to one entry type, and ⌘K (Ctrl-K) opens a command palette over everything. The full tour, including what each section shows and how the capability-gated views behave, is on the @adonis-agora/telescope-ui page.

The JSON API

The dashboard is just a client of these endpoints; you can call them directly (curl, a script, your own front end). They mount under <path>/api and each runs the authorize guard first.

EndpointReturns
GET <path>/api/entriesList entries — ?type=, ?tag=, ?traceId=, ?search=, ?before= (ISO), plus ?page= (1-based, default 1) and ?size= (capped at 500, default 50). Drops content for a compact summary.
GET <path>/api/entries/:idOne entry with its full content, or 404.
GET <path>/api/trace/:traceIdEvery entry under a trace, newest-first.
GET <path>/api/stats{ count, topFamilies, topTags }. ?limit= caps each top-N; ?type= scopes topFamilies.
GET <path>/api/retentionThe retention posture: whether a pruner is armed, its cutoff and cadence, and which entry types record below 100%. Configuration, not live pruner state.
GET <path>/api/metaWhat this install can do: extension-contributed entryTypes and dashboards, plus ai, profiling and queueManager enabled flags. Always available.
GET <path>/api/streamServer-Sent Events: every newly recorded entry, already redacted and post-sampling. Present unless stream is disabled in config/telescope.ts.

Analytics, all windowed and all reading the same stored entries the tables show:

EndpointReturns
GET <path>/api/metrics/statsLatency, throughput and error aggregates over a window.
GET <path>/api/metrics/timeseriesThe same aggregates bucketed for charting.
GET <path>/api/metrics/tracesRecent traces, newest first — paginated with ?page=/?size=.
GET <path>/api/metrics/waterfall/:traceIdOne trace laid out as a waterfall.
GET <path>/api/metrics/n-plus-one/:traceIdRepeated-query loops detected on one trace.
GET <path>/api/metrics/pulseThe Pulse health rollup. Present unless pulse is disabled.

Actions, each off by default and answering 403 until you turn it on:

EndpointReturns
POST <path>/api/requests/:id/replayRe-issues a captured request against the local server — see Request replay.
POST <path>/api/exceptions/:id/diagnoseAn AI diagnosis of one exception as Markdown, cached per family; ?force=true spends a fresh call. Needs ai.
POST <path>/api/profiles/armArms a CPU capture for the next requests.
POST <path>/api/queues/live/:queue/jobs/:id/retryRetries one job in the live queue console.
POST <path>/api/queues/live/:queue/enqueueDispatches a new job from the console.

And the read-only surfaces the console's newer views bind to:

EndpointReturns
GET <path>/api/profiles, .../profiles/:id, .../profiles/statusCaptured CPU profiles, one flamegraph, and whether profiling is armed.
GET <path>/api/schedules/liveRegistered schedules with their computed next run, joined with their last recorded run.
GET <path>/api/queues/live, .../queues/live/:queue/jobs/:idLive queue state and one job's record.
GET <path>/api/ext/:ext/data/:providerOne extension data provider's result (namespaced by owner).

Each list entry comes back as an EntrySummaryid, type, familyHash, tags, traceId, durationMs, sequence, createdAt (ISO), plus a derived one-line summary (e.g. GET /users/42 → 200, billing:invoice-paid, or an exception message).

curl 'http://localhost:3333/telescope/api/entries?type=exception&size=20&page=1'
curl 'http://localhost:3333/telescope/api/trace/abc123'
curl 'http://localhost:3333/telescope/api/stats?type=diagnostic'

Configuration

config/telescope_ui.ts
import { defineConfig } from '@adonis-agora/telescope/ui'

export default defineConfig({
  enabled: true,           // false → register no routes at all
  path: '/telescope',      // API at <path>/api/*; the telescope-ui SPA mounts the page at the root
  // authorize: (ctx) => {
  //   const { auth } = ctx as unknown as { auth: { user?: { isAdmin?: boolean } } }
  //   return auth.user?.isAdmin === true
  // },
  // credentials: { token: env.get('TELESCOPE_UI_TOKEN') },
})
KeyDefaultWhat it does
enabledtrueMaster switch. false registers no routes — the dashboard does not exist.
path/telescopeURL prefix. Normalized to a leading slash, no trailing slash.
authorizedefault policyAccess-decision hook. See Dashboard auth.
credentials{}Built-in token / basic gate for the default policy.
dashboardAuthunsetBuilt-in login page + signed session cookie. See Dashboard auth.
replay{ enabled: false }Gate for request replay.
cpuProfiling{ armEnabled: false }Gate for arming a CPU capture from the console.
queueActions{ enabled: false }Gate for the queue console's retry / enqueue actions.

Change path to something non-obvious (e.g. /__telescope) as defense in depth, but it is not a substitute for the authorize guard — covered next.

Gating the dashboard

By default the console is open outside production and denied in production unless you configure a credential or an authorize hook. The whole gate — token, HTTP Basic, a delegate-to-your-app-auth hook, and the 401-vs-403 behaviour — has its own page:

See Dashboard auth.

Build your own

Because the data lives behind the documented JSON API, the shipped dashboard is optional. You can disable it (enabled: false) and build your own UI — an internal admin page, a Grafana panel hitting the endpoints, a CLI — against the same <path>/api/* surface, or read TelescopeService directly from a controller. The dashboard is a convenience over a stable API, not a lock-in.

On this page