Table of Contents

Class QuartzDashboardOptions

Namespace
Quartz
Assembly
Quartz.Dashboard.dll

How the dashboard is served and what it is allowed to do.

public sealed class QuartzDashboardOptions
Inheritance
QuartzDashboardOptions
Inherited Members

Remarks

The dashboard renders the schedulers in its own process, through the IQuartzApiClient registered in the container. AttachStore(string, Action<IPersistentStoreBuilder>, Action<AttachStoreOptions>?) is the one thing here that adds to that set: it points the dashboard at a database and every scheduler in it becomes a window.

Properties

AuthorizationPolicy

The authorization policy the dashboard's pages, hub, circuit and assets are held to. Null — the default — applies none of its own, so what guards the dashboard is whatever the application's pipeline already does.

public string? AuthorizationPolicy { get; set; }

Property Value

string

ClusterDetectionInterval

How often the dashboard asks the targets that front schedulers of one name whether they are nodes of one cluster, and merges the ones that are into one row. Defaults to one minute; null never merges.

public TimeSpan? ClusterDetectionInterval { get; set; }

Property Value

TimeSpan?

Remarks

Two or more targets — HTTP targets with HttpClientOptions.Target, or agents — whose schedulers share a name, run on a clustered persistent store, and see at least one node in common are one cluster, listed as a+b+c/QuartzScheduler with Cluster. A target that has never answered is never merged. Membership is sticky: a member that stops answering stays in its cluster as an unreachable node, and leaves only when it answers with nodes the cluster does not have, or when its target is removed. So the key changes on a topology change only, and a remembered selection survives a node going down.

The first pass runs when the host has started, and one runs whenever a target is added or removed; this is the interval between the rest. It is also how long a dead preferred member can stay preferred: the cluster forwards what the store is to the member that answered first in the last pass, and re-elects it each pass.

DashboardPath

The base path the dashboard UI is served from. Defaults to "/quartz". A custom value is honored when the dashboard hosts its own Blazor root (the parameterless MapQuartzDashboard() overload). When integrating into an existing Blazor application the dashboard page routes are fixed at "/quartz".

public string DashboardPath { get; set; }

Property Value

string

Remarks

MapQuartzDashboard(pattern) says the same thing where the endpoints are mapped, which is where the rest of an application's routes are written, and a pattern given there wins over this.

HistoryMaxEntriesPerScheduler

How many executions and how many misfires the dashboard's own history store keeps per scheduler, oldest dropped first. Defaults to 2000 of each.

public int HistoryMaxEntriesPerScheduler { get; set; }

Property Value

int

HistoryRetention

How far back the dashboard's own history store keeps executions and misfires. Defaults to 24 hours.

public TimeSpan HistoryRetention { get; set; }

Property Value

TimeSpan

Remarks

The count bound below cannot answer for a scheduler that has gone quiet: it keeps whatever it last recorded, so a page shows executions from an arbitrary distance in the past with nothing to say how old they are. Both bounds apply, and whichever bites first wins.

IsJobTypeAllowed

Which job types a call through the dashboard's IQuartzApiClient may name. Null — the default — allows every one, which is what every earlier release did.

public Func<string, bool>? IsJobTypeAllowed { get; set; }

Property Value

Func<string, bool>

Remarks

This is the narrowing for the thing SECURITY.md says is not a vulnerability: a visitor who passes authorization can add or schedule a job of any type that implements IJob, and Quartz.Jobs' NativeJob implements it and starts the executable its job data names. Where ReadOnly refuses every mutation, this refuses one kind of them.

The predicate is given the job type name, as it was written, and nothing resolves it first — the dashboard stores a job type name unresolved for the same reason the HTTP API does, so that a name arriving from outside never makes this process probe its assemblies. Match on the string, and remember that one type has more than one spelling: Acme.Jobs.Nightly, Acme.Jobs and the same name carrying Version, Culture and PublicKeyToken are both it.

A refused name raises UnauthorizedAccessException, the way a scheduler the visitor fails SchedulerAuthorizationPolicy for does. Enforced by the client rather than by the pages, so it holds wherever the call is made from. It says nothing about the HTTP API, which is mapped and configured separately: QuartzHttpApiOptions.IsJobTypeAllowed is the same setting for that surface.

OperationAuthorizationPolicy

The authorization policy each operation is held to, evaluated against a SchedulerResource carrying the scheduler, the operation and the operator role it needs. Null — the default — leaves the dashboard as it was: whoever reaches a scheduler may do everything ReadOnly allows. Added in 4.8.

public string? OperationAuthorizationPolicy { get; set; }

Property Value

string

Remarks

Name a policy built with RequireSchedulerRoles(…) and a visitor sees the controls their role allows and no others: a viewer reads, an operator also pauses, resumes, triggers and interrupts, an administrator also adds, edits, deletes and shuts down. The operations are the HTTP API's, at the levels its route table gives them, and triggering a job with job data needs Administer.

Enforced by the client, as ReadOnly is, before anything reaches the scheduler: a refused call raises UnauthorizedAccessException, which the page shows as a toast and the Action Log records as refused, and the application's log as event 9118. Reads need View, asked with a null Operation: the picker lists the schedulers a visitor may view, and the live-events hub refuses the others.

ReadOnly stays the ceiling. A scheduler behind an agent is further bounded by the agent's own MaxAccess, and one behind an HTTP target by the target's own policy, which sees the dashboard's credential rather than the visitor's.

ReadOnly

Hides every mutating action — pause, resume, trigger now, reschedule, unschedule, delete — so the dashboard is a view of the schedulers rather than a way to drive them.

public bool ReadOnly { get; set; }

Property Value

bool

Remarks

Enforced by the client rather than by the pages alone: a mutation refused here is refused wherever it is called from, so hiding a button is not what makes it safe. It is the ceiling over OperationAuthorizationPolicy: an administrator of a read-only dashboard reads.

SchedulerAuthorizationPolicy

The authorization policy each scheduler is held to, evaluated against a SchedulerResource carrying that scheduler's name. Null — the default — leaves the dashboard as it was: whoever passes AuthorizationPolicy sees every scheduler in the process.

public string? SchedulerAuthorizationPolicy { get; set; }

Property Value

string

Remarks

Set it and the scheduler picker offers only the schedulers the visitor passes for, a page opened on one they do not says so without reading anything, and the live-events hub refuses to subscribe them to it. The policies compose: AuthorizationPolicy decides who reaches the dashboard at all, this one decides which schedulers they see once they are in, OperationAuthorizationPolicy what they may do there, and ReadOnly still bounds what anyone may change.

It is the same policy and the same resource the HTTP API's QuartzHttpApiOptions.SchedulerAuthorizationPolicy evaluates, so one AuthorizationHandler<TRequirement, SchedulerResource> answers for both surfaces.

Methods

AcceptAgents(Action<DashboardAgentHubOptions>?)

Accepts schedulers that dial out to this dashboard from other processes, through the agent hub MapQuartzDashboard then maps at {DashboardPath}/agents.

public QuartzDashboardOptions AcceptAgents(Action<DashboardAgentHubOptions>? configure = null)

Parameters

configure Action<DashboardAgentHubOptions>

The tokens, the policy, the heartbeat and the limits.

Returns

QuartzDashboardOptions

The same options, so calls can be chained.

Remarks

An agent is a worker with UseDashboardAgent from Quartz.Dashboard.Agent: it opens one outbound connection to this dashboard and keeps it open, and the dashboard lists its scheduler as Agent under {target}/{name}, with every page, every action, the history and the live events carried over that connection. Nothing is dialled towards the worker, which is what makes it the way to front a scheduler across a trust boundary or behind a network with no inbound port.

The hub authenticates agents on its own: a bearer token from Tokens, the host's authentication through AuthorizationPolicy, or both. The RequireAuthorization an application puts on what MapQuartzDashboard returns does not reach it — humans and machines authenticate differently — and a hub with neither refuses to start. The dashboard is single-instance while it accepts agents: the connections and the answers in flight are in this process's memory, and a SignalR backplane does not carry them.

AttachStore(string, Action<IPersistentStoreBuilder>, Action<AttachStoreOptions>?)

Points the dashboard at a database, so that every scheduler in it is shown as a window.

public QuartzDashboardOptions AttachStore(string target, Action<IPersistentStoreBuilder> store, Action<AttachStoreOptions>? configure = null)

Parameters

target string

The name this database is known by, which is the first half of every window's identity. It may not contain /, which separates it from the scheduler's name, or +, which joins the members of a cluster's target.

store Action<IPersistentStoreBuilder>

How to reach the database — the cluster's own store configuration.

configure Action<AttachStoreOptions>

How often the database is asked again which schedulers are in it.

Returns

QuartzDashboardOptions

The same options, so calls can be chained.

Remarks

Nothing is asked of the processes that run those schedulers: no port towards them, no plugin in them, no change to them at all. The dashboard discovers the SCHED_NAME values the database holds and builds one never-started scheduler over the same store for each — a window, Window in the listing, spelled prod/reporting wherever a scheduler name is shown. This is the right answer for a cluster behind a load balancer, where dialing one node reaches an arbitrary one of them.

store is the same IPersistentStoreBuilder callback UsePersistentStore takes, and it has to be the cluster's own: the dialect, the table prefix, the serializer, and any UseTriggerPersistenceDelegate the nodes were given. A window reads the blobs the nodes wrote, so a serializer that does not match is a page that fails on the first trigger it cannot rebuild. Add UseExecutionHistory() when the cluster keeps its history in the database, which is what puts executions on the window's History page; without it a window has no history to show, because nothing it can read was written by a node.

What a window can do is what the store is: jobs, triggers and calendars are read, added, paused, resumed, rescheduled, triggered and deleted, and whichever node picks the work up honours it. What it cannot do is anything that belongs to one process — starting, standing down, shutting down, interrupting a running job — and the pages hide those rather than offering a button that would act on a scheduler this process built and never runs.

A scheduler name the database holds that this process already has a scheduler under is refused, naming both, and the rest of the database is shown as usual.

Exceptions

ArgumentException

target is empty or contains / or +.

ArgumentNullException

store is null.

InvalidOperationException

A store is already attached under that name.