Dot.QuartzDashboard 2.3.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Dot.QuartzDashboard --version 2.3.0
                    
NuGet\Install-Package Dot.QuartzDashboard -Version 2.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Dot.QuartzDashboard" Version="2.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dot.QuartzDashboard" Version="2.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Dot.QuartzDashboard" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Dot.QuartzDashboard --version 2.3.0
                    
#r "nuget: Dot.QuartzDashboard, 2.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Dot.QuartzDashboard@2.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Dot.QuartzDashboard&version=2.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Dot.QuartzDashboard&version=2.3.0
                    
Install as a Cake Tool

Dot.QuartzDashboard

A beautiful, self-contained Quartz.NET scheduler dashboard — drop it into any ASP.NET Core app with two lines of code.

NuGet Downloads Build .NET License


🤖 AI Prompt — Copy This Into Any Copilot/AI Assistant

Use the block below to give any AI coding assistant instant, complete knowledge of this package.

You are integrating Dot.QuartzDashboard (NuGet) into an ASP.NET Core app.

PACKAGE: Dot.QuartzDashboard
TARGETS: net8.0, net9.0, net10.0
NAMESPACE: QuartzDashboard
DASHBOARD URL: /quartz (or options.Path)

--- MINIMUM SETUP (2 lines) ---

// 1. In DI (Program.cs or ServiceExtensions):
builder.Services.AddQuartzDashboard();

// 2. In middleware pipeline (before MapControllers and MapFallbackToFile):
app.UseQuartzDashboard();

That's it. Open /quartz in the browser.

--- FULL OPTIONS ---

builder.Services.AddQuartzDashboard(options =>
{
    options.Path = "/quartz";               // dashboard route prefix (default: "/quartz")
    options.Enabled = true;                  // false = UseQuartzDashboard() is a no-op
    options.ReadOnly = false;                // disable trigger/start/stop/delete actions
    options.UseSignalR = true;               // real-time push updates via SignalR

    // Auth (checked in order: auth → policy → roles)
    options.RequireAuthentication = false;   // require authenticated user (401 if not)
    options.RequiredPolicy = "";             // named IAuthorizationService policy (403 if fails)
    options.AllowedRoles = [];               // role whitelist — checked if no policy set (403 if fails)

    // History limits
    options.MaxFireHistory = 500;            // max fire records in memory (default: 500)
    options.MaxExecutionLogsPerJob = 50;     // max log lines per job
    options.HistoryRetentionHours = 24;      // auto-prune records older than this (0 = keep all)
    options.UseSystemFonts = false;          // true = use system fonts, skip 286KB embedded fonts
    options.Title = "My App Dashboard";      // custom title in sidebar + browser tab

    // Persistence (survive restarts)
    options.PersistHistoryPath = "quartz-history.json";  // persist history to disk as JSON

    // Callbacks
    options.OnJobFailed = async (jobKey, ex) => { /* Slack/PagerDuty alert */ };
    options.WebhookUrl = "https://hooks.slack.com/...";  // POST JSON on job failure
});

--- APPSETTINGS BINDING (bind a config section directly) ---

// In appsettings.json:
{
  "QuartzDashboard": {
    "Enabled": true,
    "Path": "/quartz",
    "ReadOnly": false,
    "UseSignalR": true,
    "RequireAuthentication": false,
    "RequiredPolicy": "",
    "AllowedRoles": [],
    "MaxFireHistory": 500,
    "MaxExecutionLogsPerJob": 50
  }
}

// In code:
builder.Services.AddQuartzDashboard(options =>
    builder.Configuration.GetSection("QuartzDashboard").Bind(options));

--- ENVIRONMENT GATING ---

builder.Services.AddQuartzDashboard(options =>
{
    options.Enabled = !builder.Environment.IsProduction();
});

--- MIDDLEWARE ORDER RULES ---
- UseQuartzDashboard() must come BEFORE app.MapControllers() and app.MapFallbackToFile()
- If using auth, app.UseAuthentication() and app.UseAuthorization() must come BEFORE UseQuartzDashboard()
- UseSignalR = true makes the NuGet register its own SignalR hub — do NOT manually call app.MapHub<QuartzDashboardHub>()
- The NuGet handles /quartz → /quartz/ redirect automatically (v2.1.10+)

--- API ENDPOINTS (all under {Path}/api/) ---
GET  /scheduler           - scheduler metadata, status, uptime
POST /scheduler/start     - start or resume from standby
POST /scheduler/standby   - put scheduler in standby
GET  /jobs                - all jobs with triggers (?offset=0&limit=50)
GET  /jobs/{group}/{name} - single job detail with JobDataMap
POST /jobs/{group}/{name}/trigger  - fire job immediately
POST /jobs/{group}/{name}/pause    - pause job
POST /jobs/{group}/{name}/resume   - resume job
POST /jobs/{group}/{name}/interrupt - interrupt executing job
DELETE /jobs/{group}/{name}        - delete job
GET  /triggers            - all triggers (?offset=0&limit=50)
POST /triggers/{group}/{name}/pause   - pause trigger
POST /triggers/{group}/{name}/resume  - resume trigger
GET  /executing           - currently running jobs with duration
GET  /history             - paginated fire events (?offset=0&limit=50)
GET  /stats               - per-minute execution buckets + rates + P50/P95/P99
GET  /stats/history       - rolling history for the graph
GET  /health              - scheduler health, success rate, thread pool, failure list
GET  /timeline            - execution timeline data (up to 500 records)
GET  /calendars           - calendar list
GET  /config              - dashboard config (readonly flag etc.)

--- SIGNALR HUB ---
Hub class: QuartzDashboard.QuartzDashboardHub
Default endpoint: {Path}/hub  (e.g. /quartz/hub)
Registered automatically when UseSignalR = true — no manual MapHub needed.
POST {Path}/hub/negotiate?negotiateVersion=1  → 200 when working

--- COMMON MISTAKES ---
- Do NOT call app.MapHub<QuartzDashboardHub>() yourself when UseSignalR = true
- Do NOT place UseQuartzDashboard() after MapFallbackToFile — Blazor WASM will swallow all /quartz routes
- Visiting /quartz (no trailing slash) works — NuGet redirects to /quartz/ automatically
- CSP headers: if you have a Content-Security-Policy, allow cdn.jsdelivr.net in script-src, style-src, and connect-src (Alpine.js + SignalR load from CDN)

What it does

  • See all your Quartz jobs, triggers, fire schedules, and currently executing work
  • Control the scheduler — start, standby, trigger jobs, pause/resume/delete jobs and triggers
  • Track execution history with server-side pagination, per-minute bucketed stats, and live SVG charts
  • Monitor execution rate, average duration, P50/P95/P99 percentiles, and error trends in real time
  • Visualize execution timeline with full-width color-coded bars, tooltips, and auto-fit range
  • Secure your dashboard with authentication, role-based access, and authorization policies
  • Persist fire history to disk with optional JSON file-backed storage
  • Alert on job failures via callbacks or webhook notifications
  • Zero build step — single HTML SPA with Alpine.js + Tailwind CDN, all embedded in the DLL

Quick Start

dotnet add package Dot.QuartzDashboard
// Program.cs
using QuartzDashboard;

builder.Services.AddQuartz();
builder.Services.AddQuartzHostedService();

// Line 1: register dashboard services (history tracking included automatically)
builder.Services.AddQuartzDashboard();

var app = builder.Build();

app.UseAuthentication();  // if using auth
app.UseAuthorization();   // if using auth

// Line 2: mount the dashboard (before MapControllers / MapFallbackToFile)
app.UseQuartzDashboard();

app.MapControllers();
app.Run();

Open /quartz in your browser.

Dashboard Pages

Page What you see
Overview Scheduler info + stat cards with SVG sparkline execution trends + last error card
Jobs All jobs with inline trigger details, last run time, live search/filter, server-side pagination, trigger/pause/resume/delete, batch operations
Triggers Grouped by job (accordion with persistent expand/collapse state), schedule descriptions, relative fire times
Executing Currently running jobs with animated duration bars
History Paginated fire events with relative duration bars, job filter, history count badge
Graph Dual-line SVG chart: execution count + avg duration + error rate, zoom toggles, duration overlay
Timeline Full-width color-coded execution bars with crosshair tooltip, auto-fit range, pulsing now-marker
Health Success rate, failed executions, thread pool utilization bar, scheduler diagnostics
Calendars Quartz calendars list with type badges and descriptions
Settings Refresh interval slider, per-page auto-refresh toggles, history retention info, data management

Auto-refreshes every 5 seconds. Dark/light theme with OS auto-detection. Responsive. Collapsible sidebar. Sticky table headers. Keyboard shortcuts.

Configuration

builder.Services.AddQuartzDashboard(options =>
{
    options.Path = "/admin/scheduler";    // default: "/quartz"
    options.Enabled = true;               // false = UseQuartzDashboard() is a no-op
    options.ReadOnly = false;             // disable all write actions
    options.UseSignalR = true;            // real-time updates (registers hub automatically)

    // Auth
    options.RequireAuthentication = true;
    options.AllowedRoles = ["Admin"];          // role whitelist
    options.RequiredPolicy = "CanViewDashboard"; // named policy (takes priority over roles)

    // History limits
    options.MaxFireHistory = 500;
    options.MaxExecutionLogsPerJob = 50;
    options.HistoryRetentionHours = 24;
    options.Title = "My App Dashboard";
});

Bind from appsettings.json

{
  "QuartzDashboard": {
    "Enabled": true,
    "Path": "/quartz",
    "ReadOnly": false,
    "UseSignalR": true,
    "RequireAuthentication": false,
    "RequiredPolicy": "",
    "AllowedRoles": [],
    "MaxFireHistory": 500,
    "MaxExecutionLogsPerJob": 50,
    "HistoryRetentionHours": 24,
    "Title": "QuartzDash"
  }
}
builder.Services.AddQuartzDashboard(options =>
    builder.Configuration.GetSection("QuartzDashboard").Bind(options));

Environment gating

builder.Services.AddQuartzDashboard(options =>
{
    options.Enabled = !builder.Environment.IsProduction();
});

Authentication & Authorization

Three levels, checked in order:

  1. RequireAuthentication — unauthenticated requests → 401
  2. RequiredPolicy — uses IAuthorizationService (named policy) → 403 on failure
  3. AllowedRoles — role whitelist, checked if no policy is set → 403 on failure
// Role-based
builder.Services.AddQuartzDashboard(options =>
{
    options.RequireAuthentication = true;
    options.AllowedRoles = ["Admin", "Operator"];
});

// Policy-based
builder.Services.AddAuthorization(o =>
    o.AddPolicy("RequireDashboardAccess", p => p.RequireRole("Admin")));

builder.Services.AddQuartzDashboard(options =>
{
    options.RequireAuthentication = true;
    options.RequiredPolicy = "RequireDashboardAccess";
});

Middleware Placement

app.UseAuthentication();   // ← must be BEFORE UseQuartzDashboard if using auth
app.UseAuthorization();    // ← must be BEFORE UseQuartzDashboard if using auth

app.UseQuartzDashboard();  // ← BEFORE MapControllers and MapFallbackToFile

app.MapControllers();
app.MapFallbackToFile("index.html"); // e.g. Blazor WASM

⚠️ Blazor WASM users: placing UseQuartzDashboard() after MapFallbackToFile will cause all /quartz requests to return index.html instead of the dashboard.

API Endpoints

All endpoints under {basePath}/api/ (default: /quartz/api/).

Scheduler

Method Path Description
GET /scheduler Metadata, status, uptime, version
POST /scheduler/start Start / resume from standby
POST /scheduler/standby Pause scheduler

Jobs

Method Path Description
GET /jobs All jobs with triggers + schedule descriptions (paginated: ?offset=0&limit=50)
GET /jobs/{group}/{name} Single job detail with JobDataMap
POST /jobs/{group}/{name}/trigger Fire job immediately
POST /jobs/{group}/{name}/pause Pause job
POST /jobs/{group}/{name}/resume Resume job
POST /jobs/{group}/{name}/interrupt Interrupt executing job
DELETE /jobs/{group}/{name} Delete job

Triggers

Method Path Description
GET /triggers All triggers with schedule descriptions (paginated: ?offset=0&limit=50)
GET /triggers/{group}/{name} Single trigger detail
POST /triggers/{group}/{name}/pause Pause trigger
POST /triggers/{group}/{name}/resume Resume trigger

Runtime

Method Path Description
GET /executing Currently executing jobs with duration
GET /history Paginated fire events (?offset=0&limit=50&job=key)
GET /stats Per-minute execution buckets, rate, avg duration, P50/P95/P99
GET /stats/history Rolling history for the graph
GET /health Success rate, thread pool utilization, failure list
GET /timeline Execution timeline data (up to 500 records)
GET /calendars Quartz calendars list
GET /config Dashboard config snapshot

SignalR Real-Time Updates

When UseSignalR = true (default), the NuGet registers its own hub automatically:

Hub endpoint: {Path}/hub  (e.g. /quartz/hub)

Do NOT call app.MapHub<QuartzDashboardHub>() yourself — it is handled internally.

To verify the hub is active:

curl -X POST http://localhost:5000/quartz/hub/negotiate?negotiateVersion=1
# → 200 OK = working

History & Stats

AddQuartzDashboard() automatically registers an IJobListener that:

  • Records the last N fire events (configurable via MaxFireHistory, default 500)
  • Optionally persists history to a JSON file (PersistHistoryPath) — survives restarts
  • Auto-prunes records older than HistoryRetentionHours (default 24h)
  • Buckets executions per-minute into 120 rolling ExecutionBucket entries
  • Tracks per-bucket: count, total duration, error count
  • Powers /api/stats, /api/stats/history, and the SVG execution graph

No external storage required — in-memory by default (~7 KB for 120 buckets). Optional file persistence for production resilience.

Testing

# Run core unit tests (95 tests)
dotnet test QuartzDashboard.Tests -c Release

# Run integration tests (61 tests — real WebApplicationFactory with Quartz scheduler)
dotnet test QuartzDashboard.IntegrationTests -c Release

# Run all tests (156 total)
dotnet test -c Release

Integration tests verify: endpoint responses, auth flows, config options, SignalR hub connectivity, read-only mode, host app coexistence, and history tracking — all with a realistic simulated API host.

Common Issues

Symptom Cause Fix
/quartz returns Blazor index.html UseQuartzDashboard() placed after MapFallbackToFile Move it before
Dashboard loads but SignalR shows amber/disconnected Hub not registered Set UseSignalR = true (default), do not manually call MapHub
CDN scripts blocked Strict Content-Security-Policy header Add cdn.jsdelivr.net to script-src, style-src, connect-src
401 on all dashboard requests RequireAuthentication = true but user not logged in Add auth middleware before UseQuartzDashboard()
Jobs show count in header but no rows render Alpine.js nested x-for bug (pre-2.1.23) Update to 2.1.23+ — jobs table now uses a flat single x-for
Stats/history endpoints return empty Old package version (pre-2.1.28) required a separate AddQuartzDashboardHistory() call Update to 2.1.28+
Old app.js loaded from browser cache Pre-2.1.26 used hardcoded ?v=2.1.8 Update to 2.1.26+ — asset URLs are now version-busted automatically

Architecture

Request → app.Use() (inline middleware, path-matched to basePath)
          ├── /hub/*       → pass through to MapHub endpoint routing (SignalR)
          ├── /api/*       → HandleApi (route by path segments)
          ├── /quartz      → 302 redirect → /quartz/ (v2.1.10+)
          ├── /app.js      → embedded static file
          ├── /app.css     → embedded static file
          └── anything else → SPA fallback (embedded index.html)
  • Backend: Raw ASP.NET Core app.Use() middleware — zero routing conflicts with controllers
  • Frontend: Single HTML file with Alpine.js 3.x + Tailwind CSS v4 CDN (all embedded in the DLL)
  • Target frameworks: net8.0, net9.0, net10.0
  • Dependencies: Quartz 3.18.0, Quartz.Extensions.DependencyInjection 3.18.0
  • Strong-named: Assembly is signed for GAC/enterprise scenarios

Demo

cd QuartzDashboard.Demo

dotnet run                     # default port 5190
dotnet run -- -p 8080          # custom port
dotnet run -- --auth           # enable cookie auth (test access control)
dotnet run -- --readonly       # disable write actions
dotnet run -- -p 5000 --auth --readonly

6 demo jobs with diverse schedules: HealthCheck (15s), CacheWarmup (30s), ReportGeneration (2min), DataSync (CRON :00/:30), UnstableImport (~30% fail rate), ManualNotification (durable, fire from UI).

Changelog

See CHANGELOG.md for the full version history.

v2.2.0 (2026-05-09)

  • Server-side table pagination for jobs, triggers, and history
  • Full-width execution timeline (removed offset bug)
  • 61 integration tests via WebApplicationFactory
  • Light mode contrast overhaul, sticky headers, skeleton animations, favicon
  • CI integration test job in GitHub Actions

v2.1.47 (2026-05-09)

  • Trigger accordion state persists on refresh (keyed by stable job name)

v2.1.46 (2026-05-09)

  • Timeline/history limit increased to 500, history count badge, last error card, jobs last run column, fit button, thread pool bar, pulsing now marker

v2.1.43 (2026-05-09)

  • Skeleton loading polish, consistent sort ordering across all pages

v2.1.26 (2026-05-04)

  • Fixed: browser caching — asset URLs now include the package version as a query string

License

MIT — use it, ship it, open-source it.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.2.0 87 5/16/2026
4.1.0 90 5/12/2026
4.0.1 111 5/11/2026
4.0.0 86 5/11/2026
3.0.6 88 5/11/2026
3.0.5 93 5/11/2026
3.0.4 95 5/10/2026
3.0.3 105 5/10/2026
3.0.2 89 5/10/2026
2.4.5 106 5/9/2026
2.4.1 94 5/9/2026
2.3.2 96 5/9/2026
2.3.1 91 5/9/2026
2.3.0 92 5/9/2026
2.2.0 95 5/9/2026
2.1.47 94 5/9/2026
2.1.46 94 5/9/2026
2.1.45 90 5/9/2026
2.1.44 91 5/9/2026
2.1.43 98 5/9/2026
Loading failed