Dot.QuartzDashboard
2.4.1
See the version list below for details.
dotnet add package Dot.QuartzDashboard --version 2.4.1
NuGet\Install-Package Dot.QuartzDashboard -Version 2.4.1
<PackageReference Include="Dot.QuartzDashboard" Version="2.4.1" />
<PackageVersion Include="Dot.QuartzDashboard" Version="2.4.1" />
<PackageReference Include="Dot.QuartzDashboard" />
paket add Dot.QuartzDashboard --version 2.4.1
#r "nuget: Dot.QuartzDashboard, 2.4.1"
#:package Dot.QuartzDashboard@2.4.1
#addin nuget:?package=Dot.QuartzDashboard&version=2.4.1
#tool nuget:?package=Dot.QuartzDashboard&version=2.4.1
Dot.QuartzDashboard
A beautiful, self-contained Quartz.NET scheduler dashboard — drop it into any ASP.NET Core app with two lines of code.
🤖 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
- Search globally across jobs, triggers, and history with
Ctrl+K - Navigate with keyboard shortcuts — press
?to see all - Inspect execution details — click any history row for full stacktraces and metadata
- Build CRON expressions visually with the built-in builder and presets
- Embed the dashboard in iframes with
?embed=true(strips sidebar/header) - 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, webhooks, or the health notification badge
- Zero build step — single HTML SPA with Alpine.js + Tailwind, 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 mobile layout with bottom tab bar. Collapsible sidebar. Sticky table headers. Sortable columns. Keyboard shortcuts (? to see all). Global search (Ctrl+K). Branded boot loader. SignalR reconnection toasts. Data pulse indicator. Embed mode (?embed=true).
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:
RequireAuthentication— unauthenticated requests → 401RequiredPolicy— usesIAuthorizationService(named policy) → 403 on failureAllowedRoles— 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()afterMapFallbackToFilewill cause all/quartzrequests to returnindex.htmlinstead 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 | /heatmap |
Execution density grid (day-of-week × hour-of-day with success rates) |
| GET | /calendars |
Quartz calendars list |
| GET | /schedulers |
All registered schedulers (name, instance ID, status) |
| 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
ExecutionBucketentries - 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:
Quartz3.18.0,Quartz.Extensions.DependencyInjection3.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.3.1 (2026-05-09)
- Branded boot loader with phase text while dashboard initializes
- SignalR reconnection toasts (drop/reconnect/polling fallback feedback)
- Data pulse indicator in footer when live data arrives
- Fixed hardcoded version in About section
v2.3.0 (2026-05-09)
- Global search (
Ctrl+K) across jobs, triggers, and history - Keyboard shortcuts overlay (
?) with full navigation shortcuts - Execution detail drawer — click history rows for stacktraces and metadata
- CRON expression builder with visual editor and presets
- Execution heatmap (day-of-week × hour-of-day density grid)
- Sortable column headers on jobs, triggers, and history tables
- Empty state illustrations when no data exists
- Health notification badge (red dot when success rate < 95%)
- Mobile responsive bottom nav bar on screens < 768px
- Breadcrumb navigation in header
- Embed mode (
?embed=true) for iframe integration - Multi-scheduler endpoint and UI infrastructure
- Backend:
/api/heatmapand/api/schedulersendpoints
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 | Versions 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. |
-
net10.0
- Quartz (>= 3.18.0)
- Quartz.Extensions.DependencyInjection (>= 3.18.0)
-
net8.0
- Quartz (>= 3.18.0)
- Quartz.Extensions.DependencyInjection (>= 3.18.0)
-
net9.0
- Quartz (>= 3.18.0)
- Quartz.Extensions.DependencyInjection (>= 3.18.0)
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 |