Skip to content

Dashboard and monitoring

Secure the dashboard and monitor or manage jobs through its UI, API and JobMonitor.

6 min read
dotnet add package Immediate.Jobs.Dashboard --prerelease

Configure and register the dashboard before building the application. Then map its UI and API:

using Immediate.Jobs.Dashboard;

var traceExplorer = new Uri("https://traces.example/");
var logExplorer = new Uri("https://logs.example/");

builder.Services.AddMyAppJobs()
	.ConfigureStorage(storage => storage.UseInMemory())
	.AddImmediateJobsDashboard()
	.ConfigureDashboard(options => options.AuthorizationPolicy = "operations")
	.AddTelemetryLink(
		"View execution trace",
		JobTelemetryLinkKind.Trace,
		context => context.Execution?.ExecutionTraceId is { } traceId
			? new(traceExplorer, $"trace/{traceId}")
			: null
	)
	.AddTelemetryLink(
		"View execution logs",
		JobTelemetryLinkKind.Logs,
		context => context.Execution is { } execution
			? new(logExplorer,
				$"search?jobHandle={Uri.EscapeDataString(context.Job.JobHandle.Value)}&attempt={execution.Attempt}")
			: null
	)
	.AddTelemetryLink(
		"View all retry logs",
		JobTelemetryLinkKind.Logs,
		context => context.Execution is null
			? new(logExplorer, $"search?jobHandle={Uri.EscapeDataString(context.Job.JobHandle.Value)}")
			: null
	);

var app = builder.Build();
app.MapImmediateJobsDashboard("/jobs");

Chain AddImmediateJobsDashboard from the generated jobs registration. Set dashboard options with ConfigureDashboard; the mapping call only selects the URL path. ConfigureDashboard also accepts an OptionsBuilder<ImmediateJobsDashboardOptions> action. Use it to bind an IConfiguration section:

builder.Services.AddMyAppJobs()
	.ConfigureStorage(storage => storage.UseInMemory())
	.AddImmediateJobsDashboard()
	.ConfigureDashboard(options => options.Bind(
		builder.Configuration.GetSection("ImmediateJobs:Dashboard")));

BindConfiguration("ImmediateJobs:Dashboard") binds the same section from the configuration registered with dependency injection. Jobs validates the settings when the host starts.

By default, every dashboard endpoint is limited to the Development environment and returns 403 elsewhere. Setting AuthorizationPolicy uses that policy instead of the environment check. For a trusted custom development environment, you can remove the default restriction without setting a policy:

builder.Services.AddMyAppJobs()
	.ConfigureStorage(storage => storage.UseInMemory())
	.AddImmediateJobsDashboard()
	.ConfigureDashboard(options => options.RestrictToDevelopmentEnvironment = false);

var app = builder.Build();
app.MapImmediateJobsDashboard("/jobs");

Treat the dashboard as an administrative tool because it exposes job inputs, failures, IDs and actions that change job state. Set AuthorizationPolicy whenever the dashboard is available outside a trusted development environment. The policy protects both the UI and API.

The UI shows queue and state totals, recent history, job details, recurring schedules, scheduler servers and batches. It also shows workflow graphs when storage supports them.

Dashboard UI

Inspect jobs

The Jobs view lists saved jobs and their current state. Select a job to see its payload and saved attempts, newest first. Each attempt includes its state, worker, timing, trace IDs and failure text. Failed jobs offer Retry. Scheduled jobs and delayed retries offer Run now, which moves the same job to Pending without changing its attempt count or failure history. Jobs that have not finished offer Cancel with a confirmation step.

Follow batch workflows

The Batches view shows the jobs in a batch and their dependencies. It distinguishes skipped branches from cancelled work. Select a job without leaving the workflow view. A running batch offers Cancel with a confirmation step.

The Immediate.Jobs dashboard batch workflow graph in light mode, showing succeeded jobs and their continuations
A batch workflow showing each job and its dependencies.

AddTelemetryLink adds application-defined links to job and execution details. For each link, the dashboard calls your URL function with a JobTelemetryLinkContext.

ArgumentPurpose
labelUser-facing description of the destination.
kindTrace or Logs; controls how the dashboard identifies the link.
createUrlBuilds the destination from context.Job and optional context.Execution; return null to hide it.

For a job link, context.Execution is null and the execution fields on context.Job describe the latest attempt. For an attempt link, context.Execution and the execution fields on context.Job both describe the selected attempt. Use Execution for links to one attempt. Use Job.JobHandle when a destination should search across every retry. Its Value property is the raw string expected by URL builders.

The URL function may return HTTP(S) or dashboard-relative URLs. Other absolute URI schemes are rejected. Return null before an execution trace exists or whenever a destination does not apply to the current record.

HTTP endpoints

All paths below are relative to the mapped prefix.

Dashboard JSON names job identifiers jobHandle and batch identifiers batchHandle. Their values remain opaque strings on the wire even though the .NET monitoring records use JobHandle and BatchHandle.

Method and pathPurpose
GET /api/overviewCurrent counts and supported storage features.
GET /api/jobsFilter by state, queue, search; skip; take 1–200 (default 50).
GET /api/jobs/{jobHandle}Latest saved record.
GET /api/jobs/{jobHandle}/executionsSaved attempts newest first; skip; take 1–200 (default 50).
GET /api/jobs/{jobHandle}/telemetry-linksConfigured trace and log links for a job.
GET /api/jobs/{jobHandle}/executions/{executionNumber}/telemetry-linksConfigured links for one saved attempt.
POST /api/jobs/{jobHandle}/cancelCancel a job that has not finished.
POST /api/jobs/{jobHandle}/retryRetry failed work or run scheduled work now.
GET /api/recurringRecurring schedules.
POST /api/recurring/{name}/triggerStart one run now.
POST /api/recurring/{name}/pause / resumeChange schedule state.
GET /api/serversRecently active workers.
GET /api/batchesFilter by state, skip, take 1–500 (default 100).
GET /api/batches/{batchHandle}Batch status.
GET /api/batches/{batchHandle}/membersFilter and page through jobs in a batch.
GET /api/batches/{batchHandle}/graphJobs and dependencies in a batch.
POST /api/batches/{batchHandle}/cancelCancel jobs in a batch that have not finished.
DELETE /api/batches/{batchHandle}Delete a completed batch.
GET /api/eventsSSE state snapshots at UpdateInterval.
GET /api/batches/{batchHandle}/streamSSE status and graph events on change.

Actions return these status codes:

StatusMeaning
202A recurring run was accepted.
204A cancel, retry, pause, resume or batch action succeeded.
400A route or paging value is invalid.
404The item does not exist, or storage does not support the requested feature.
409The item’s current state does not allow the action.

Live endpoints use server-sent events (SSE). They send retry: 3000, disable proxy buffering and close when the request is aborted. The server checks for changes on a timer instead of keeping an event log, so clients should reload after reconnecting.

Use JobMonitor in code

Inject the scoped JobMonitor for custom status pages and administrative endpoints. It reads snapshots, jobs, saved attempts, batches, batch members and workflow graphs. It can also cancel or retry jobs, cancel or delete batches, and pause, resume or trigger recurring schedules. The dashboard uses this same service.

IJobMonitor exposes only the read methods. Use it when code needs no management commands or a test needs a simple replacement. Do not use IJobStorage for application monitoring or management. It is for storage providers and the Jobs runtime.

Monitor methods accept JobHandle and BatchHandle. Scheduling calls already return those types. At an HTTP or database boundary, convert a raw identifier with JobHandle.FromString or BatchHandle.FromString. QueryExecutionsAsync takes the job handle separately from its paging and attempt filter.

QueryExecutionsAsync returns attempts newest first. Execution history remains with its job or batch until that record is deleted or purged. Batch reads return null when the provider does not support graphs, so non-batch monitoring still works with Redis and other queue-only providers.

Monitoring snapshots include only scheduler servers whose last heartbeat is at most two minutes old. SQL providers remove stale server rows on later heartbeats, while Redis expires them. Apply paging and authorization to custom endpoints because payload and failure data can contain business-sensitive values.