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
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
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
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
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
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
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
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
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
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
configureAction<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
targetstringThe 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.storeAction<IPersistentStoreBuilder>How to reach the database — the cluster's own store configuration.
configureAction<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
targetis empty or contains/or+.- ArgumentNullException
storeis null.- InvalidOperationException
A store is already attached under that name.