Dashboard

Inspecting schedules in the DevTools and Voltro Cloud dashboards — discovered jobs, firing history, status, and "Run now".

Every discovered schedule shows up in the Schedules panel of both the local DevTools dashboard (voltro dev) and the Voltro Cloud dashboard. Same component, two transports: DevTools polls the app's inspect endpoints directly; Cloud streams firing history live via its reactive bridge.

What you see

Per schedule:

  • Name, cron expression, timezone, and the absolute next-firing time in the browser locale.
  • Trigger badge — self or external.
  • The effective coordination strategy for the app (single / advisoryLock / cluster), shown once at the top.
  • overlap, backfill, and max runtime settings.
  • A Run now button (capability-gated) — fires the handler immediately, bypassing the clock and coordination, recorded as a manual run.

Per firing (expand a schedule's run timeline):

  • Status — succeeded, failed, skipped, missed, or running, each colour-toned.
  • When it fired (relative + absolute), how long it took, and which replica ran it.
  • The coordinationOutcome (single / wonLock / cluster / external) — why this instance ran it.
  • For failures: the errorTag and message (e.g. SmtpError: connection refused, or timeout from the watchdog).

Reading the statuses

Status Meaning
succeeded Handler completed within maxRuntimeMs.
failed Handler threw, or the watchdog tripped (errorTag: timeout).
skipped An overlapping firing under onOverlap: 'skip'.
missed A firing skipped during downtime, recorded by backfill: 'latest'. Visible evidence of a gap, not a silent hole.
running In flight right now.
queued Under onOverlap: 'queue': an occurrence waiting for its turn behind an earlier one, anywhere in the fleet. A row that stays queued while nothing runs belongs to a replica that stopped reporting — the next firing adopts it, stamping itself as the replicaId, or records it missed if it has been silent past the adoption bound.

missed and skipped rows matter: they're the system telling you a firing didn't run and why. A wall of missed after a deploy means your downtime crossed firing instants — expected with backfill: 'skip', a signal to consider latest if those runs mattered.

Externally driven schedules show whether they are being driven

A trigger: 'external' schedule carries a badge when the most recent occurrence that has passed has no run behind it.

It exists because in that mode nothing else notices silence. The app arms no timer, so a platform scheduler that stopped firing writes nothing at all — and "no runs" is exactly what a schedule with nothing to do looks like. The badge is the difference between those two, and voltro_schedule_external_missed_total is the same fact as a series to alert on.

The badge is shown only when the server answered both halves. An app too old to report them renders as unknown, not as healthy: a panel that says "fine" because nobody asked is how a monitoring gap gets certified.

"Run now"

The Run now button starts the handler out-of-band and returns its run ID as soon as the firing is recorded. The HTTP request does not wait for handler completion; follow the running, succeeded or failed status in the firing history. The run is tagged trigger: 'manual' and always executes regardless of onOverlap — operators expect the button to fire. Inside the handler you can branch on ctx.trigger === 'manual' to skip schedule-only guards (e.g. a weekday check) during a manual test.

It's gated on the same capability as workflow run-control, so read-only dashboard viewers see the history but can't trigger firings.

The endpoints behind it

The dashboard is a thin client over the app's inspect API — useful if you're scripting:

GET  /_voltro/inspect/schedules            # discovered schedules + effective coordination
GET  /_voltro/inspect/schedules/runs?name= # firing history for one schedule
POST /_voltro/inspect/schedules/:name/fire # "Run now"

In the Cloud dashboard, firing history is live — the cloud API mirrors each app's inspect stream into a reactive cache, so new runs appear without a refresh. In local DevTools the panel polls every few seconds.

See workflow debugging for the analogous Workflows panel.

Backfill a time range

Expand a schedule and use Backfill a time range to recover an outage or rerun a range. Enter the start and end in the browser timezone shown beside the form. The server uses the schedule’s cron expression and timezone to find occurrences in (from, to].

The dashboard submits at most 1,000 occurrences per request. More than 25 requires a second confirmation showing the occurrence count. A range above the cap must be narrowed; confirmation does not bypass the cap. Changing either date clears the confirmation. The progress display reports finished occurrences and failures; consult the firing history for individual results. Previously completed side effects can run again.

The endpoint returns HTTP 409 with a structured kind: 'refused' result when confirmation or a smaller range is required. Both dashboard transports retain that outcome instead of replacing it with a generic request error.

POST /_voltro/inspect/schedules/:name/backfill-requests
{ "requestId": "backfill-request-1", "from": "2026-09-01T00:00:00Z", "to": "2026-09-03T00:00:00Z", "confirm": false, "limit": 1000 }

Run controls require the app’s separate read and write inspect credentials and an operator role in Cloud. See dashboard access.

Run controls ignore repeated submissions while a request is pending. Before sending a backfill, the dashboard stores its request ID and range for this app and schedule in the browser. After a reload, expanding the schedule reads the same request and its progress. If acceptance is unknown, Retransmit request uses the same ID; it does not create a second backfill. The range remains locked until completion or interruption. Prepare a new request explicitly clears the previous selection without submitting anything. Use Start backfill separately to begin the new request. If browser storage is unavailable, the dashboard reports the error before sending any work.

The expanded firing history filters by the declared schedule name. Its total counts all matching firings, independently of the requested page size.

At the runtime handle level, startBackfillRange(name, from, to, { cap, requestId }) persists a backfill request and returns before sequential execution finishes. backfillRequest(requestId) reads its stored cursor, active occurrence and per-run outcomes. Repeating the same request ID observes the original request; a different range with that ID is rejected. An ambiguous dispatch is marked interrupted rather than retried automatically.

While a stored backfill is executing, its owner updates a heartbeat every five seconds. A request read or recovery sweep marks an owner silent for more than 30 seconds as interrupted. This is an uncertain execution outcome, not permission to replay an active occurrence. Scheduler shutdown also marks its owned requests interrupted; completed requests remain unchanged.

Stored backfill requests are available through POST /_voltro/inspect/schedules/:name/backfill-requests (read and write credentials) and GET /_voltro/inspect/schedules/backfill-requests/:requestId (read credential). Choose requestId before sending. Acceptance returns HTTP 202 with { kind: "accepted", request }; confirmation or cap refusal returns 409 with kind: "refused". Repeating an accepted ID observes the same request. Reusing it for a different name or range returns a conflict.

{ "requestId": "backfill-request-1", "from": "2026-09-01T00:00:00Z", "to": "2026-09-03T00:00:00Z", "confirm": true, "limit": 1000 }

The stored backfill also exposes Recorded results: each settled occurrence shows its status, scheduled time and full run ID. Results are paginated in groups of 20 and remain available to read-only viewers, even when those runs are no longer in the recent firing list. A completed range does not imply that every occurrence succeeded; failed, skipped and missed outcomes remain distinct.

To read one firing outside the recent-history window, call GET /_voltro/inspect/schedules/runs?name=<schedule>&runId=<run-id>. URL-encode both values. The optional runId filter is combined with name; a missing run or a run from another schedule returns an empty runs array and total: 0. This is a read-authorized Inspect request; no write credential is required.

Choose View run on a recorded result to open its details in place: current status, scheduled/start/completion times, duration, replica, coordination outcome and recorded error. The detail is read by its original run ID directly from the app, including on the hosted dashboard; it does not depend on the recent-history cache. Only an opened result makes detail requests. Loading, connection errors and a run that is no longer stored are distinguished; its recorded backfill outcome remains visible. Shared dashboard hosts supply useScheduleRun(name, runId) with a DataSource<ScheduleRun | null>; null means the exact run was not found.

Invalid inspection responses

The dashboard validates schedule lists and firing history before rendering them. A malformed response produces an explicit error instead of a crash or an empty-list message. In local DevTools, Load again requests a fresh snapshot immediately and cancels the previous request; late responses cannot replace the new result. Automatic polling continues. If the error persists, check that the inspected app and dashboard use compatible framework versions.