Dot.QuartzDashboard
2.2.0
See the version list below for details.
dotnet add package Dot.QuartzDashboard --version 2.2.0
NuGet\Install-Package Dot.QuartzDashboard -Version 2.2.0
<PackageReference Include="Dot.QuartzDashboard" Version="2.2.0" />
<PackageVersion Include="Dot.QuartzDashboard" Version="2.2.0" />
<PackageReference Include="Dot.QuartzDashboard" />
paket add Dot.QuartzDashboard --version 2.2.0
#r "nuget: Dot.QuartzDashboard, 2.2.0"
#:package Dot.QuartzDashboard@2.2.0
#addin nuget:?package=Dot.QuartzDashboard&version=2.2.0
#tool nuget:?package=Dot.QuartzDashboard&version=2.2.0
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 = 100; // max fire records in memory
options.MaxExecutionLogsPerJob = 50; // max log lines per job
options.UseSystemFonts = false; // true = use system fonts, skip 286KB embedded fonts
});
--- APPSETTINGS BINDING (bind a config section directly) ---
// In appsettings.json:
{
"QuartzDashboard": {
"Enabled": true,
"Path": "/quartz",
"ReadOnly": false,
"UseSignalR": true,
"RequireAuthentication": false,
"RequiredPolicy": "",
"AllowedRoles": [],
"MaxFireHistory": 100,
"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 and schedule descriptions
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 with schedule descriptions
POST /triggers/{group}/{name}/pause - pause trigger
POST /triggers/{group}/{name}/resume - resume trigger
GET /executing - currently running jobs with duration
GET /history - last N fire events
GET /stats - per-minute execution buckets + rates
GET /stats/history - rolling history for the graph
GET /health - scheduler health, success rate, failure list
GET /timeline - execution timeline data
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 per-minute bucketed stats and live SVG charts
- Monitor execution rate, average duration, and error trends in real time
- Secure your dashboard with authentication, role-based access, and authorization policies
- 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 |
| Jobs | All jobs with inline trigger details, live search/filter, trigger/pause/resume/delete |
| Triggers | Grouped by job (accordion), schedule descriptions, relative fire times |
| Executing | Currently running jobs with animated duration bars |
| History | Last N fire events with relative duration bars, job filter |
| Graph | Dual-line SVG chart: execution count + avg duration, zoom toggles |
| Timeline | Color-coded execution dots with tooltips, real-time now-line |
| Health | Success rate, failed executions, pool utilization, scheduler diagnostics |
| Calendars | Quartz calendars list with type badges and descriptions |
| Settings | Refresh interval slider, per-page auto-refresh toggles, data management |
Auto-refreshes every 5 seconds. Dark theme, responsive, collapsible sidebar.
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 = 100;
options.MaxExecutionLogsPerJob = 50;
});
Bind from appsettings.json
{
"QuartzDashboard": {
"Enabled": true,
"Path": "/quartz",
"ReadOnly": false,
"UseSignalR": true,
"RequireAuthentication": false,
"RequiredPolicy": "",
"AllowedRoles": [],
"MaxFireHistory": 100,
"MaxExecutionLogsPerJob": 50
}
}
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 |
| 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 |
| 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 |
Last N fire events |
| GET | /stats |
Per-minute execution buckets, rate, avg duration |
| GET | /stats/history |
Rolling history for the graph |
| GET | /health |
Success rate, pool utilization, failure list |
| GET | /timeline |
Execution timeline data |
| 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 in a
ConcurrentQueue<FireRecord>(configurable viaMaxFireHistory) - 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 — all in-memory, ~7 KB for 120 buckets.
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
5 demo jobs with diverse schedules: HealthCheck (15s), CacheWarmup (30s), ReportGeneration (2min), DataSync (CRON :00/:30), ManualNotification (durable, fire from UI).
Changelog
v2.1.26 (2026-05-04)
- Fixed: browser caching — asset URLs now include the package version as a query string (
app.js?v=2.1.26,charts.js?v=2.1.26,app.css?v=2.1.26), busting the browser cache on every release - The version is injected at serve time from the assembly version — no hardcoded strings
v2.1.24 (2026-05-04)
- Improved timeline UX: vertical crosshair line follows cursor position
- Hovering the timeline now shows a tooltip with the time at cursor and all executions whose bars overlap that X position
- Hovering a specific bar highlights it and shows its exact duration and status
- Row highlight follows mouse Y position (not just bar hover events)
- Fixed: tooltip / row highlight no longer gets stuck after chart auto-refresh
v2.1.23 (2026-05-04)
- Fixed: jobs table was rendering 0 rows when jobs were grouped (nested
x-forinside a<table>is not supported reliably in Alpine.js v3) - Replaced nested
x-forwith a flatjobRowsgetter returning interleaved group-header and job items, rendered with a singlex-for
v2.1.10 (2026-05-04)
- Fixed:
/quartz(no trailing slash) now automatically redirects to/quartz/— relative asset URLs (app.js,app.css) were resolving to the wrong base path
v2.1.9 (2026-05-04)
- Consolidated setup:
UseSignalR = truenow registers the SignalR hub internally — no manualapp.MapHub<QuartzDashboardHub>()required - All configuration keys aligned with
QuartzDashboardOptionsproperty names (Path,RequiredPolicy,TrackHistory)
v2.0.0 (2026-05-03)
- Breaking:
QuartzDashboardOptionsadditions:Enabled,RequireAuthentication,AllowedRoles,RequiredPolicy,MaxFireHistory,MaxExecutionLogsPerJob UseQuartzDashboard()is a no-op whenEnabled = false- Authentication support with role-based and policy-based authorization
- Strong-named assembly for GAC/enterprise scenarios
- Package icon and SourceLink support
- New demo CLI flags:
-p,--auth,--readonly
v1.0.0 (2026-05-02)
- Complete UI/UX overhaul: glassmorphism, collapsible sidebar, animations, responsive
- Live execution graph: SVG dual-line chart with zoom toggles and tooltips
- New
/api/statsendpoint with per-minute execution buckets - Schedule descriptions on triggers ("Every 00:00:30", CRON expressions)
- Expandable job rows with inline trigger details
v0.3.0 (2026-05-02)
- Fixed routing via
app.Use()for Blazor WASM compatibility (replacedapp.Map())
v0.2.0 (2026-05-02)
- Raw middleware approach, all endpoints verified
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 |