{"id":50060810,"url":"https://github.com/anwarminarso/a2n.hangfire.dashboard","last_synced_at":"2026-07-06T18:00:22.708Z","repository":{"id":357778876,"uuid":"1238276447","full_name":"anwarminarso/a2n.Hangfire.Dashboard","owner":"anwarminarso","description":"Open-source Hangfire dashboard extension (Blazor Server) with console output, job tags, recurring job admin, search, filters, analytics, and realtime updates.","archived":false,"fork":false,"pushed_at":"2026-06-24T15:34:52.000Z","size":2646,"stargazers_count":6,"open_issues_count":3,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-03T04:31:23.620Z","etag":null,"topics":["aspnetcore","background-jobs","blazor","bootstrap","dashboard","dotnet","hangfire","monitoring","open-source","realtime","signalr"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/a2n.Hangfire.Dashboard","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anwarminarso.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"anwarminarso","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":["https://sociabuzz.com/anwarminarso"]}},"created_at":"2026-05-14T01:26:03.000Z","updated_at":"2026-06-24T13:45:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"b3673eea-3900-46ba-8493-fc956c233ee0","html_url":"https://github.com/anwarminarso/a2n.Hangfire.Dashboard","commit_stats":null,"previous_names":["anwarminarso/a2n.hangfire.dashboard"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/anwarminarso/a2n.Hangfire.Dashboard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anwarminarso%2Fa2n.Hangfire.Dashboard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anwarminarso%2Fa2n.Hangfire.Dashboard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anwarminarso%2Fa2n.Hangfire.Dashboard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anwarminarso%2Fa2n.Hangfire.Dashboard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anwarminarso","download_url":"https://codeload.github.com/anwarminarso/a2n.Hangfire.Dashboard/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anwarminarso%2Fa2n.Hangfire.Dashboard/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35201088,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-06T02:00:07.184Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspnetcore","background-jobs","blazor","bootstrap","dashboard","dotnet","hangfire","monitoring","open-source","realtime","signalr"],"created_at":"2026-05-21T19:00:21.691Z","updated_at":"2026-07-06T18:00:22.614Z","avatar_url":"https://github.com/anwarminarso.png","language":"C#","funding_links":["https://github.com/sponsors/anwarminarso","https://sociabuzz.com/anwarminarso"],"categories":[],"sub_categories":[],"readme":"# a2n.Hangfire.Dashboard\n\n**A modern, open-source dashboard extension for [Hangfire](https://www.hangfire.io/)** — analytics, console logs, tags, and recurring job management built in.\n\nOpen source (LGPL-3.0-or-later). Requires Hangfire 1.8+ and ASP.NET Core (.NET 8, 9, or 10). Not officially supported by Hangfire OÜ.\n\n[![NuGet](https://img.shields.io/nuget/v/a2n.Hangfire.Dashboard)](https://www.nuget.org/packages/a2n.Hangfire.Dashboard)\n[![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-512BD4)](https://dotnet.microsoft.com/)\n[![License](https://img.shields.io/badge/license-LGPL--3.0--or--later-blue.svg)](LICENSE)\n[![Hangfire](https://img.shields.io/badge/Hangfire-1.8+-blue.svg)](https://www.hangfire.io/)\n\n---\n\n![Dashboard Overview](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/overview.png)\n\n## Get Started in 30 Seconds\n\n```bash\ndotnet add package a2n.Hangfire.Dashboard\n```\n\n```csharp\n// Use in place of app.UseHangfireDashboard():\nbuilder.Services.AddHangfireDashboardUI();\napp.UseHangfireDashboardUI(\"/hangfire\");\n```\n\nNavigate to `/hangfire`. Done.\n\n\u003e **Authorization:** By default, only **local requests** are allowed (same as the Hangfire dashboard). For remote access, set `Authorization = []` or add your own filters. See [CHANGELOG.md](CHANGELOG.md) and [`samples/SampleAppAuth`](samples/SampleAppAuth) for a cookie login example.\n\n---\n\n## 🚀 What's New in 2.5 — Recurring Schedule Heatmap\n\nPlan controllable cron jobs around **real on-demand load**. The new **Schedule Heatmap** projects your recurring jobs — and actual ad-hoc demand — onto a **queue × day × hour** grid so you can spot collisions, find the quietest window to schedule, and rebalance load. See the [showcase below](#-recurring-schedule-heatmap).\n\n| | Feature | What you get |\n|---|---------|--------------|\n| 🗺️ | **Seven views** | Planner (cron over ad-hoc demand with low-load \"safe windows\"), Punchcard, Queue × Hour, Per-queue, Calendar, Concurrency, and stagger Recommendations. |\n| 🔀 | **Projected + Historical** | A storage-agnostic **Projected** source (from cron expressions) on any storage; a **Historical** source and ad-hoc demand overlay on SQL Server / PostgreSQL — or on any storage via the new rollup adapters. Degrades gracefully where unavailable. |\n| 🧰 | **Rollup metrics for non-SQL storage** ([#21](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/21)) | New `a2n.Hangfire.Dashboard.Rollup` / `a2n.Hangfire.Dashboard.Redis` packages bring Analytics, heatmap Historical, and realistic **estimated durations** to Redis, in-memory, and other NoSQL storages — no SQL required. |\n| 🔎 | **Drill-down \u0026 a11y** | Click any cell to inspect the jobs scheduled in that slot. Honors per-job \u0026 viewer time zones, light/dark theme, and is keyboard / screen-reader accessible. |\n\nSee the [changelog](CHANGELOG.md) for the full list.\n\n---\n\n## 🚀 What's New in 2.4 — Job Builder\n\nCreate and schedule jobs **with their arguments** directly from the dashboard — no code change, no redeploy. This closes a long-standing gap: the recurring editor previously built jobs with empty arguments, so parameterized methods couldn't be scheduled correctly ([#8](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/8)).\n\n\u003e **2.4.3 (patch)** — UI/UX fixes: the Failed Jobs table no longer hides the \"Failed\" column behind a horizontal scrollbar when an exception is long ([#17](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/17)); the Create Job method dropdown keeps its Contract/Implementation pill aligned for long names ([#18](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/18)); the Recurring Jobs search no longer drops characters when typing quickly ([#19](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/19)); and the dark theme now persists across navigation and new sessions ([#20](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/20)). See the [changelog](CHANGELOG.md).\n\n\u003e **2.4.2 (patch)** — recurring jobs now accept mixed-case/dotted ids and editable never-fire cron expressions ([#11](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/11)); a client-side filter on the Recurring Jobs page ([#13](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/13)); long job names no longer break the table layout ([#12](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/12)); a duplicate-id guard when creating; and the Audit Log page gains an items-per-page selector and paged navigation. See the [changelog](CHANGELOG.md).\n\n\u003e **2.4.1 (patch)** — searchable method picker; contract-aware resolution so interface/abstract targets keep their class-level `[Tag]`/`[Queue]` and display names; fixed editing a recurring job whose method takes an injected parameter ([#10](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/10)); consistent solid-red Delete buttons across all job pages. See the [changelog](CHANGELOG.md).\n\n| | Feature | What you get |\n|---|---------|--------------|\n| 🧩 | **Guided parameter form** | A type-aware form generated from the method signature — text, numbers, dates, GUIDs, enums, booleans, arrays, and nested objects — with a live **JSON mirror** and a Form ⇄ JSON toggle. [→](#job-builder) |\n| 🔎 | **Method discovery** | Pick from methods discovered across loaded assemblies (decorated with `JobDisplayName`, `Tag`, or `Queue`) via a **searchable picker** that badges each entry as Contract or Implementation, with **overload-safe** resolution. Hand-typed arbitrary methods are opt-in via `AllowArbitraryMethodInvocation`. [→](#job-builder) |\n| 🕑 | **Visual cron builder** | Build a cron schedule field-by-field (every / specific / range / step) with a human-readable description and **next-run preview** in the selected time zone — or type a cron string manually. [→](#job-builder) |\n| ➕ | **Enqueue page** | A new `/jobs/enqueue` page reuses the same builder to fire one-off jobs, not just recurring ones. [→](#job-builder) |\n| ✏️ | **Typed args + edit pre-fill** | Values are converted to the method's declared types (`[\"report\", 42]` → `string`, `int`); injected parameters (`PerformContext`, `CancellationToken`) are skipped; existing argument values are pre-filled when editing. [→](#job-builder) |\n\n```csharp\napp.UseHangfireDashboardUI(\"/hangfire\", new DashboardUIOptions\n{\n    EnableJobManagement           = true,   // show the recurring create/edit builder and Enqueue page\n    AllowArbitraryMethodInvocation = false, // opt-in: allow hand-typed type+method (default false)\n});\n```\n\nSee [Job Builder](#job-builder) for the full feature description.\n\n---\n\n## 🚀 What's New in 2.3.0 — Operational Visibility \u0026 Controls\n\nThe biggest operations-focused release yet. The dashboard goes from a **viewer you open when something breaks** to a **first-class operational tool**.\n\n| | Feature | What you get |\n|---|---------|--------------|\n| 🩺 | **Health checks** | `/healthz` · `/healthz/ready` · `/healthz/full` endpoints for Kubernetes \u0026 load balancers, an ASP.NET Core `IHealthCheck` adapter, and a traffic-light **hero card** on the Home page. [→](#health-checks) |\n| ⏸️ | **Queue pause / resume** | Pause a single queue from the new card-based `/queues` page. Workers reschedule jobs instead of running them — **no data loss**. [→](#operations) |\n| 🚧 | **Maintenance mode** | One global toggle pauses every queue, with a persistent banner on every page. [→](#operations) |\n| 📋 | **Audit log** | Every admin action recorded — who, when, what — on a new `/audit` page, attributed to the real signed-in user. [→](#operations) |\n| 🔗 | **Enhanced Job Details** | Continuation **dependency graph**, a **retry summary banner**, and clickable **stack-trace source links** (GitHub/GitLab/Azure DevOps/Bitbucket/local IDE). [→](#job-dependency-graph) |\n\n```csharp\n// Liveness/readiness probes + queue-pause enforcement in one place:\nbuilder.Services.AddHangfire(config =\u003e config\n    .UseSqlServerStorage(connStr)\n    .UseDashboardQueuePauseFilter());        // honour pause toggles on running servers\n\nbuilder.Services.AddHealthChecks()\n    .AddHangfireDashboard(tags: new[] { \"ready\" });   // unified /health endpoint\n```\n\nSee the full [2.3.0 changelog](CHANGELOG.md) for every option and detail.\n\n---\n\n## About\n\nHangfire ships a capable monitoring UI out of the box. Many teams extend it with community packages for additional dashboard features — for example [Hangfire.Console](https://github.com/pieceofsummer/Hangfire.Console), [Hangfire.Tags](https://github.com/face-it/Hangfire.Tags), and [Hangfire.RecurringJobAdmin](https://github.com/bamotav/Hangfire.RecurringJobAdmin). Dashboard analytics are also available in [Hangfire Pro](https://www.hangfire.io/pricing/).\n\n**a2n.Hangfire.Dashboard** combines several of these capabilities in one extension: search, filters, analytics (with optional storage adapters), console viewer, tags, recurring job CRUD, SignalR realtime updates, and theme options — while using the same Hangfire job storage and APIs.\n\n---\n\n## Features\n\n| Feature | Description |\n|---------|-------------|\n| Job monitoring | Job state pages, batch operations, servers, retries |\n| Recurring jobs | Create, edit, start, and stop recurring jobs from the UI, with a client-side id/name filter |\n| Schedule Heatmap | 🆕 Visualize recurring-job density by queue × day × hour and plan cron around real demand — Planner, Punchcard, Calendar, Concurrency, Recommendations, and more (see [Schedule Heatmap](#-recurring-schedule-heatmap)) |\n| Job Builder | 🆕 Create \u0026 schedule jobs **with typed arguments** — guided parameter form (+ JSON), method discovery, overload-safe resolution, and one-off enqueue at `/jobs/enqueue` — see [Job Builder](#job-builder) |\n| Visual cron builder | 🆕 Build cron schedules field-by-field with a human-readable description and next-run preview |\n| Console output | Logs, progress bars, and colors (Hangfire.Console-compatible API) |\n| Job tags | Tagging and tag cloud (Hangfire.Tags-compatible storage) |\n| Job dependency graph | 🆕 Continuation pipeline visualization on the Job Details page (with \"Load more\" expansion) |\n| Retry summary | 🆕 Inline banner above state history showing retry count + exception consistency |\n| Stack trace links | 🆕 File references in stack traces become clickable links to GitHub/GitLab/Azure DevOps/Bitbucket/local IDE |\n| Global search | Search by job ID, name, queue, tag, or exception text |\n| Advanced filters | Filter by date, duration, state, server, and more |\n| Analytics | Throughput, latency, failures, queue health (requires storage adapter — see [Packages](#packages)) |\n| Health checks | 🆕 `/healthz` endpoints (liveness, readiness, full report) + at-a-glance hero card on Home — see [Health Checks](#health-checks) |\n| Queue pause / maintenance | 🆕 Pause individual queues or enable global maintenance mode — see [Operations](#operations) |\n| Audit log | 🆕 Every admin action recorded (who, when, what) — filterable, paged — see [Operations](#operations) |\n| Realtime updates | Live metrics via SignalR |\n| Authorization | Local-only default (same as Hangfire); optional async filters and `LoginPath` redirect |\n| Theming | Dark, light, or auto; responsive layout |\n| Tech | Blazor Server, Bootstrap 5, Chart.js |\n\n---\n\n## Screenshots\n\n### ⭐ Recurring Schedule Heatmap\n\nProject your recurring (cron) jobs — and real ad-hoc demand — onto a **queue × day × hour** grid to spot collisions, find the quietest window to schedule, and rebalance load. Multiple views, click-through drill-down, and (with a storage adapter) a Historical/Demand source.\n\n| Planner — cron over ad-hoc demand |\n|:---:|\n| ![Schedule Heatmap Planner](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/heatmap.png) |\n\n| Punchcard | Calendar |\n|:---:|:---:|\n| ![Punchcard](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/heatmap-punchcard.png) | ![Calendar](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/heatmap-calendar.png) |\n\n| Concurrency | Overlap Recommendations |\n|:---:|:---:|\n| ![Concurrency](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/heatmap-concurrency.png) | ![Recommendations](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/heatmap-recommendations.png) |\n\n| Cell drill-down — jobs scheduled in a slot |\n|:---:|\n| ![Drill-down](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/heatmap-drilldown.png) |\n\n### More\n\n| Home \u0026 Realtime Charts | Console Viewer |\n|:---:|:---:|\n| ![Home](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/home.png) | ![Console](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/console-viewer.png) |\n\n| Tags \u0026 Search | Recurring Jobs |\n|:---:|:---:|\n| ![Tags](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/tags.png) | ![Recurring](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/recurring.png) |\n\n| Advanced Search | Analytics |\n|:---:|:---:|\n| ![Advanced Search](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/search.png) | ![Analytics](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/performance.png) |\n\n| Enqueue Job (typed args) | Recurring Schedule (Cron Builder) |\n|:---:|:---:|\n| ![Enqueue Job](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/enqueue-job.png) | ![Recurring Schedule](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/recurring-schedule.png) |\n\n| Light / Dark / Auto |\n|:---:|\n| ![Light / Dark / Auto theme](https://raw.githubusercontent.com/anwarminarso/a2n.Hangfire.Dashboard/main/docs/screenshots/light-dark.png) |\n\n---\n\n## Packages\n\n| NuGet package | Purpose |\n|---------------|---------|\n| [`a2n.Hangfire.Dashboard`](https://www.nuget.org/packages/a2n.Hangfire.Dashboard) | Main dashboard UI (search, console, tags, recurring admin) |\n| [`a2n.Hangfire.Dashboard.SqlServer`](https://www.nuget.org/packages/a2n.Hangfire.Dashboard.SqlServer) | Storage-specific queries + full analytics for SQL Server |\n| [`a2n.Hangfire.Dashboard.PostgreSql`](https://www.nuget.org/packages/a2n.Hangfire.Dashboard.PostgreSql) | Storage-specific queries + full analytics for PostgreSQL |\n| [`a2n.Hangfire.Dashboard.Rollup`](https://www.nuget.org/packages/a2n.Hangfire.Dashboard.Rollup) | Rollup-based analytics for non-SQL storages (Redis, in-memory, other NoSQL) |\n| [`a2n.Hangfire.Dashboard.Redis`](https://www.nuget.org/packages/a2n.Hangfire.Dashboard.Redis) | Convenience entry point for Redis — registers rollup metrics (`UseRedisStorage()`) |\n| [`a2n.Hangfire.Console`](https://www.nuget.org/packages/a2n.Hangfire.Console) | Console integration (Hangfire.Console-compatible API) |\n| [`a2n.Hangfire.Tags`](https://www.nuget.org/packages/a2n.Hangfire.Tags) | Tags integration (Hangfire.Tags-compatible storage) |\n\nWithout a storage adapter package, search and core dashboard features work; the **Analytics** pages and heatmap **Historical** source require a metrics adapter:\n\n- **SQL Server / PostgreSQL** — `UseSqlServerStorage()` / `UsePostgreSqlStorage()` (direct SQL queries; best accuracy)\n- **Redis / in-memory / other non-SQL** — `UseRedisStorage()` or `UseRollupMetrics()` (incremental rollup; forward-only historical data)\n\n```csharp\n// Redis (Hangfire Pro or community StackExchange.Redis storage already configured)\nbuilder.Services.AddHangfireDashboardUI(options =\u003e options.UseRedisStorage());\n\n// Any other non-SQL Hangfire storage\nbuilder.Services.AddHangfireDashboardUI(options =\u003e options.UseRollupMetrics());\n```\n\n---\n\n## Full Setup\n\n```csharp\nusing a2n.Hangfire.Dashboard;\nusing Hangfire;\nusing Hangfire.Console;  // Hangfire.Console-compatible API (bundled in this package)\nusing Hangfire.Tags;     // Hangfire.Tags-compatible API (bundled in this package)\n\nvar builder = WebApplication.CreateBuilder(args);\n\nvar connectionString = builder.Configuration.GetConnectionString(\"Hangfire\")\n    ?? throw new InvalidOperationException(\"Connection string 'Hangfire' not found.\");\n\nbuilder.Services.AddHangfire(config =\u003e config\n    .UseSqlServerStorage(connectionString)  // or .UsePostgreSqlStorage(connectionString)\n    .UseConsole()        // Enable console output\n    .UseTags());         // Enable job tagging\n\nbuilder.Services.AddHangfireServer();\n\n// Basic setup (search works; analytics requires a storage adapter — see Packages)\nbuilder.Services.AddHangfireDashboardUI();\n\n// With storage adapter for storage-specific queries + full analytics:\n// builder.Services.AddHangfireDashboardUI(options =\u003e\n// {\n//     options.UseSqlServerStorage(connectionString);\n//     // options.UsePostgreSqlStorage(connectionString);\n// });\n\nvar app = builder.Build();\n\napp.UseHangfireDashboardUI(\"/hangfire\", new DashboardUIOptions\n{\n    DashboardTitle = \"My Jobs\",\n    DefaultTheme = \"auto\",  // \"auto\", \"light\", or \"dark\"\n    EnableJobManagement = true,  // set false to hide Create/Edit/Stop and the Enqueue page\n    JobGraphMaxDepth = 5,   // continuation graph traversal depth (default 5)\n    JobGraphMaxNodes = 30,  // continuation graph node budget (default 30)\n    // SourceLink = SourceLinkOptions.GitHub(\"owner/repo\"),  // clickable stack-trace file links\n});\n\napp.Run();\n```\n\n### Project Reference (Development Only)\n\nIf referencing via `\u003cProjectReference\u003e` instead of NuGet, add this to your host `.csproj`:\n\n```xml\n\u003cPropertyGroup\u003e\n  \u003cRequiresAspNetWebAssets\u003etrue\u003c/RequiresAspNetWebAssets\u003e\n\u003c/PropertyGroup\u003e\n```\n\nNuGet consumers don't need this — it's handled automatically.\n\n---\n\n## Using with existing Hangfire code\n\n### Console \u0026 tags compatibility\n\nAlready using `Hangfire.Console` or `Hangfire.Tags`? You can keep those packages, or switch to the bundled ones — job code and storage formats are compatible:\n\n```csharp\n// Works exactly the same\ncontext.WriteLine(\"Processing order...\");\ncontext.WriteProgressBar();\n\n[Tag(\"orders\")]\npublic void ProcessOrder() { }\n```\n\n### Existing DashboardOptions? Still works.\n\n```csharp\napp.UseHangfireDashboardUI(\"/hangfire\", new DashboardOptions\n{\n    DashboardTitle = \"My Jobs\",\n    Authorization = new[] { new MyAuthFilter() },\n});\n```\n\n### Data compatibility\n\nReads the same storage format as [Hangfire.Console](https://github.com/pieceofsummer/Hangfire.Console) and [Hangfire.Tags](https://github.com/face-it/Hangfire.Tags). Existing console logs and tags are visible immediately — no migration needed.\n\n---\n\n## Job Builder\n\nThe Job Builder lets operators construct, schedule, and enqueue Hangfire jobs **with their arguments** from the dashboard — no code change or redeploy. It powers two places:\n\n- **Recurring** — the Create/Edit recurring job form (`/recurring`), gated behind `EnableJobManagement`.\n- **Enqueue** — a new one-off job page at `/jobs/enqueue` for fire-and-forget jobs, also gated behind `EnableJobManagement`.\n\nBoth share the same method picker, parameter form, and argument conversion, so behavior is identical across the two.\n\n### Method discovery \u0026 selection\n\nThe picker offers two sources:\n\n- **Registered methods** — discovered by scanning loaded assemblies for `public` instance/static methods whose method or declaring class carries a recognized attribute (`JobDisplayName`, `Tag`, or `Queue`). Discovery is cached for the dashboard's lifetime and resilient to assemblies that fail to load. Interface and abstract-class contracts are surfaced alongside their concrete implementations, each badged as **Contract** or **Implementation**, and the list is searchable by display label, type name, or method name.\n- **Custom methods** — a full type name + method name typed by hand, validated on demand. This is **opt-in** and disabled by default:\n\n```csharp\napp.UseHangfireDashboardUI(\"/hangfire\", new DashboardUIOptions\n{\n    AllowArbitraryMethodInvocation = true,   // default false — keeps arbitrary invocation opt-in\n});\n```\n\nResolution is **overload-safe**: when a method name has multiple overloads, the single overload whose parameter count and types match the supplied arguments is selected. Ambiguous, missing, or non-matching methods are rejected with an identifying error and never touch storage.\n\n### Parameter form\n\nA type-aware form is generated from the method signature, one control per parameter:\n\n| Parameter type | Control |\n|----------------|---------|\n| `string` | text input |\n| integers / floats | number input (whole vs. fractional, range-checked) |\n| `DateOnly` / `TimeOnly` / `DateTime` / `DateTimeOffset` | date / time / datetime-local picker |\n| `Guid` | text input |\n| `bool` / `bool?` | checkbox / tri-state (unset = null) |\n| enum / `[Flags]` enum | single-select / multi-select |\n| scalar arrays (`T[]`) | add/remove list |\n| nested object (class, depth ≤ 5) | collapsed placeholder + explicit \"Add\" to instantiate |\n| anything else / depth \u003e 5 | JSON textarea |\n\nKey behaviors:\n\n- **Form ⇄ JSON toggle** — switch to a raw `[\"value\", 42]` JSON textarea at any time; the form and JSON stay in sync (a read-only JSON mirror is shown alongside the form).\n- **Typed conversion** — values are converted to each parameter's declared CLR type before the job is stored, matching how Hangfire serializes arguments.\n- **Injected parameters skipped** — `PerformContext`, `IJobCancellationToken`, and `CancellationToken` are excluded from the form and filled by Hangfire at runtime.\n- **Empty fields are total** — a blank field resolves to `null` for nullable types or `default(T)` for non-nullable types, never an error.\n- **Edit pre-fill** — when editing an existing recurring job, current argument values are loaded back into the form.\n\n### Schedule builder (recurring only)\n\nFor recurring jobs, the Schedule Builder offers a field-by-field cron editor (minute, hour, day-of-month, month, day-of-week) with an **Every / Specific / Range / Step** mode per field, plus a manual cron input. It shows a human-readable description and the **next occurrence** in the selected time zone (UTC when none is chosen). Unparseable expressions are flagged and block submission. (Cron parsing uses Cronos, already bundled with Hangfire — no new dependency.)\n\n### Queue handling\n\nThe queue control is editable with suggestions from current queues (defaulting to `default`). When the target method or its declaring class carries a `[Queue(...)]` attribute, the control becomes read-only and shows a precedence notice, because Hangfire's `QueueAttribute` overrides the stored queue at state election.\n\n### Gating\n\n- **Read-only mode** (`IsReadOnly = true`) — a persistent banner is shown and the submit controls are disabled.\n- **Job management** (`EnableJobManagement = false`) — the recurring create/edit builder is hidden, the navigation no longer links to the Enqueue page, and the `/jobs/enqueue` route returns Not Found (read-only gating still applies). Defaults to `true`.\n- **Arbitrary methods** (`AllowArbitraryMethodInvocation = false`, default) — only discovered registered methods may be selected.\n\n---\n\n## Job Dependency Graph\n\nThe Job Details page includes a continuation visualization for jobs created via `BackgroundJob.ContinueJobWith(...)`. The graph walks up to the root parent (via the `Awaiting` state's `ParentId`), then expands all descendants by parsing each job's `Continuations` parameter. Edge labels show the continuation condition (`on succeeded`, `on deleted`, `on any`).\n\nThe card only appears when a job is part of a continuation chain — standalone jobs render nothing. Each node is clickable and navigates to that job's details. Expired or deleted jobs render as dashed placeholders so the graph stays consistent.\n\nTraversal is bounded by two options to keep page loads fast:\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `JobGraphMaxDepth` | `5` | Maximum hops in either direction (ancestors or descendants) |\n| `JobGraphMaxNodes` | `30` | Maximum total nodes materialized |\n\nWhen either limit is hit, the card shows a `truncated` badge and a **Load more** button that doubles the node budget and adds depth (`+3`) on each click, up to a hard ceiling of 200 nodes / depth 12. Each click triggers exactly one storage round-trip per newly visited node, so expansion is incremental.\n\n---\n\n## Stack Trace Source Links\n\nWhen a job fails, the Job Details page shows the exception stack trace. By default the stack trace is plain text. Set `DashboardUIOptions.SourceLink` to turn `... in {path}:line {N}` references into clickable links pointing to your source provider:\n\n```csharp\napp.UseHangfireDashboardUI(\"/hangfire\", new DashboardUIOptions\n{\n    DashboardTitle = \"My Jobs\",\n    SourceLink = SourceLinkOptions.GitHub(\"anwarminarso/a2n.Hangfire.Dashboard\", branch: \"main\"),\n});\n```\n\n### Built-in presets\n\n```csharp\nSourceLinkOptions.GitHub(\"owner/repo\");                                        // github.com\nSourceLinkOptions.GitLab(\"group/repo\");                                        // gitlab.com\nSourceLinkOptions.GitLab(\"group/repo\", host: \"git.mycompany.com\");             // self-hosted GitLab\nSourceLinkOptions.AzureDevOps(\"org\", \"project\", \"repo\");                       // dev.azure.com\nSourceLinkOptions.Bitbucket(\"workspace/repo\");                                 // bitbucket.org\nSourceLinkOptions.Local();                                                     // vscode://file/{path}:{line}\nSourceLinkOptions.Local(protocol: \"vscode-insiders\");                          // VS Code Insiders\nSourceLinkOptions.Local(protocol: \"cursor\");                                   // Cursor\n```\n\n### Self-hosted / proprietary providers\n\nUse `UrlPattern` directly with `{path}` and `{line}` placeholders:\n\n```csharp\nSourceLink = new SourceLinkOptions\n{\n    UrlPattern = \"https://gitea.mycompany.com/team/repo/src/branch/main/{path}#L{line}\",\n};\n```\n\n### Visual Studio (Windows)\n\nVisual Studio does not ship with a built-in URL protocol handler. To enable links that open files in Visual Studio, install a third-party handler such as [VsHandler](https://marketplace.visualstudio.com/items?itemName=Slvier.VsHandler) and configure:\n\n```csharp\nSourceLink = new SourceLinkOptions\n{\n    UrlPattern = \"vs://open?File={absolutePath}\u0026Line={line}\",\n};\n```\n\nFor VS Code (cross-platform), the built-in `Local()` preset works out of the box.\n\n### Path normalization\n\nStack traces often contain absolute paths from the build agent (e.g. `C:\\jenkins\\workspace\\proj\\src\\Foo.cs`) that don't match the repository layout. Use `WithPathStrip(\"src\")` to strip everything before the `/src/` segment:\n\n```csharp\nSourceLink = SourceLinkOptions.GitHub(\"owner/repo\")\n    .WithPathStrip(\"src\");\n// \"C:\\jenkins\\workspace\\proj\\src\\Models\\Order.cs\" → \"src/Models/Order.cs\"\n```\n\nFor more complex transforms, use `WithPathReplace(pattern, replacement)` (regex) or set `PathTransform` to a custom `Func\u003cstring, string\u003e`.\n\n### Privacy note\n\nLinked URLs only work when the viewer has access to the source provider. Private repositories return 404 to unauthenticated users. The dashboard does not embed source content — it only generates outbound links.\n\n---\n\n## Health Checks\n\nThe dashboard exposes a structured health endpoint suitable for Kubernetes probes, load balancer health checks, and status pages.\n\n| Endpoint | Use case | Checks |\n|----------|----------|--------|\n| `GET /{dashboard}/healthz` | Liveness probe (K8s) | Storage probe |\n| `GET /{dashboard}/healthz/ready` | Readiness probe (K8s) | Storage + servers |\n| `GET /{dashboard}/healthz/full` | Status pages, dashboard hero card | All six checks below |\n\nHTTP status follows the K8s convention: `200` for `Healthy` or `Degraded`, `503` for `Unhealthy`.\n\n### Built-in checks\n\n| Key | What it covers |\n|-----|----------------|\n| `storage` | Round-trip time of a `GetStatistics()` call to the Hangfire storage backend |\n| `servers` | Total vs alive vs stale heartbeat (per `ServerHeartbeatTolerance`) |\n| `queue_depth` | Highest queue length compared to warn/critical thresholds |\n| `stuck_processing` | Processing jobs older than `StuckProcessingMinutes` (sample size: 200) |\n| `failure_rate` | Last-hour failure percentage from Hangfire's hourly counters |\n| `recurring_jobs` | Recurring jobs whose `NextExecution` is older than `RecurringMissedTolerance` |\n\n### Sample response (`/hangfire/healthz/full`)\n\n```json\n{\n  \"status\": \"Degraded\",\n  \"version\": \"2.3.1\",\n  \"timestamp\": \"2026-06-07T01:08:18.96Z\",\n  \"durationMs\": 50,\n  \"checks\": {\n    \"storage\":          { \"status\": \"Healthy\",  \"data\": { \"responseTimeMs\": 3 } },\n    \"servers\":          { \"status\": \"Degraded\", \"description\": \"1 of 2 server(s) have stale heartbeats.\" },\n    \"queue_depth\":      { \"status\": \"Healthy\",  \"data\": { \"maxDepth\": 0 } },\n    \"stuck_processing\": { \"status\": \"Healthy\",  \"data\": { \"stuckCount\": 0, \"totalProcessing\": 0 } },\n    \"failure_rate\":     { \"status\": \"Degraded\", \"description\": \"Failure rate 20.0% in last hour (warn threshold 10%).\" },\n    \"recurring_jobs\":   { \"status\": \"Healthy\",  \"data\": { \"total\": 7, \"missed\": 0 } }\n  }\n}\n```\n\n### Configuration\n\n```csharp\napp.UseHangfireDashboardUI(\"/hangfire\", new DashboardUIOptions\n{\n    // AllowAnonymous (default) so K8s/LB probes work without auth.\n    // Use LocalOnly for loopback-only probes, or RequireDashboardAuth for the full filter chain.\n    HealthCheckAuthorizationMode = HealthCheckAuthorization.AllowAnonymous,\n\n    HealthCheckThresholds = new HealthThresholds\n    {\n        StuckProcessingMinutes      = 30,\n        QueueDepthWarn              = 1000,\n        QueueDepthCritical          = 10000,\n        FailureRatePercent          = 10.0,   // Degraded threshold\n        FailureRateCritical         = 25.0,   // Unhealthy threshold\n        RecurringMissedTolerance    = TimeSpan.FromMinutes(5),\n        ServerHeartbeatTolerance    = TimeSpan.FromSeconds(60),\n        StorageResponseTimeWarnMs   = 1000,\n        StorageResponseTimeCriticalMs = 5000,\n    }\n});\n```\n\n### Plug into ASP.NET Core HealthChecks\n\nIf your host already exposes a unified `/health` endpoint aggregating multiple dependencies (database, message broker, Hangfire, ...), register the dashboard as an `IHealthCheck`:\n\n```csharp\nbuilder.Services.AddHealthChecks()\n    .AddHangfireDashboard(tags: new[] { \"ready\" });\n\napp.MapHealthChecks(\"/health\");                                   // all checks\napp.MapHealthChecks(\"/health/ready\",                               // readiness subset\n    new() { Predicate = c =\u003e c.Tags.Contains(\"ready\") });\n```\n\nThe adapter reuses the same `HealthCheckService.CheckFull()` so results match what the dashboard's own endpoint and hero card show.\n\n### Hero card\n\nThe Home page shows a top-of-page traffic light (Healthy / Degraded / Critical) with per-issue descriptions and deep-link actions (\"View processing →\", \"View failed →\"). Auto-refreshes every 10 seconds. The detailed 8-card stat grid is now collapsed behind a \"Detailed metrics\" toggle to keep the hero front-and-center.\n\n---\n\n## Operations\n\nThe dashboard ships day-to-day operational controls so ops teams don't need to redeploy or write SQL to handle common scenarios.\n\n### Queue pause / resume\n\nThe `/queues` page lists every queue with its current status and a per-row pause toggle. Paused queues stop workers from executing jobs — the dashboard's server filter intercepts each fetched job and reschedules it back into the future (default +30s) instead of running it.\n\n```csharp\n// Required: register the server filter on the Hangfire pipeline so running servers\n// honour the pause toggles set from the dashboard.\nbuilder.Services.AddHangfire(config =\u003e config\n    .UseSqlServerStorage(connStr)\n    .UseDashboardQueuePauseFilter());          // \u003c-- here\n```\n\nWithout this call, the dashboard still records the pause and shows the badge — but jobs keep executing on running workers until the host restarts with the filter enabled.\n\nCustomize behaviour via `DashboardUIOptions.QueueOperations`:\n\n```csharp\nQueueOperations = new QueueOperationsOptions\n{\n    Enabled         = true,\n    Behavior        = PausedJobBehavior.Reschedule,   // or Requeue (busy-loop, use sparingly)\n    RescheduleDelay = TimeSpan.FromSeconds(30),\n    PauseStateCacheTtl = TimeSpan.FromSeconds(2),     // server-side cache window\n}\n```\n\n### Maintenance mode\n\nA single global toggle (top of the `/queues` page) pauses every queue at once. While maintenance is active, a persistent yellow banner is rendered on every dashboard page with the operator's reason and a `Manage →` link. Disable maintenance to resume — individual queue pauses set before maintenance was enabled remain in effect.\n\n### Audit log\n\nEvery admin action performed through the dashboard is recorded:\n\n| Category | Actions |\n|----------|---------|\n| Job actions | requeue, delete, batch requeue, batch delete |\n| Recurring | create, update, delete, trigger, stop, start |\n| Queue ops | queue pause, queue resume, maintenance enabled, maintenance disabled |\n\nEach entry captures: timestamp (UTC), user (or `(anonymous)` for unauthenticated local requests), client IP, action, target, optional reason, and a small metadata bag (e.g., batch counts).\n\nThe `/audit` page filters by action prefix (job/jobs/recurring/queue/maintenance), user (substring), and target, with an items-per-page selector and paged navigation. Storage uses Hangfire's KV primitives — no schema changes. Configurable retention:\n\n```csharp\nAuditLog = new AuditLogOptions\n{\n    Enabled    = true,\n    Retention  = TimeSpan.FromDays(30),\n    MaxEntries = 10_000,\n}\n```\n\nOld entries beyond either bound are trimmed on writes (best-effort, ~every 50 entries).\n\n---\n\n## Tech Stack\n\n| Layer | Technology |\n|-------|-----------|\n| UI | Blazor Server (Interactive SSR) |\n| Styling | Bootstrap 5.3 + Bootstrap Icons |\n| Charts | Chart.js + chartjs-plugin-streaming |\n| Realtime | ASP.NET Core SignalR |\n| Theme | `data-bs-theme` + localStorage persistence |\n| Targets | .NET 8, .NET 9, .NET 10 |\n\n## Project Structure\n\n```\nsrc/\n├── a2n.Hangfire.Dashboard/             # Main dashboard (Blazor + SignalR + Analytics)\n├── a2n.Hangfire.Dashboard.SqlServer/   # SQL Server adapter (Dapper + T-SQL)\n├── a2n.Hangfire.Dashboard.PostgreSql/  # PostgreSQL adapter (Dapper + Npgsql)\n├── a2n.Hangfire.Dashboard.Rollup/      # Rollup metrics engine for non-SQL storages\n├── a2n.Hangfire.Dashboard.Redis/       # Redis / non-SQL entry point (UseRedisStorage)\n├── a2n.Hangfire.Console/               # Console integration (Hangfire.Console-compatible)\n└── a2n.Hangfire.Tags/                  # Tags integration (Hangfire.Tags-compatible)\n\ntests/\n├── a2n.Hangfire.Dashboard.Tests/\n├── a2n.Hangfire.Console.Tests/\n└── a2n.Hangfire.Dashboard.PostgreSql.Tests/\n\nsamples/\n├── SampleApp/          # Full demo (all features)\n├── SampleAppAuth/      # Cookie authentication example\n├── SampleAppMvc/       # ASP.NET Core MVC host\n├── SampleAppRazor/     # Razor Pages host\n├── SampleAppBlazor/    # Blazor host\n└── SampleAppOrig/      # Startup-class host pattern\n```\n\n## Running the Sample\n\n```bash\ngit clone https://github.com/anwarminarso/a2n.Hangfire.Dashboard.git\ncd a2n.Hangfire.Dashboard/samples/SampleApp\ndotnet run\n```\n\nOpen `https://localhost:7100/hangfire` to see it in action.\n\nFor authentication with a login page, run `samples/SampleAppAuth` instead.\n\n---\n\n## Roadmap\n\n| Version | Status | Scope |\n|---------|--------|-------|\n| v1.0 | ✅ Done | Core dashboard pages + Console + Tags + Recurring Admin |\n| v1.1 | ✅ Done | Global search \u0026 advanced filters |\n| v1.2 | ✅ Done | Razor Class Library (NuGet-ready) |\n| v1.3–v1.6 | ✅ Done | Storage adapters (SQL Server, PostgreSQL) + Analytics dashboard |\n| v2.0 | ✅ Done | Feature-complete for current scope |\n| v2.1 | ✅ Done | Search refactor + JobDisplayName + SQL Server fixes |\n| v2.1.1 | ✅ Done | WebSocket fix for Startup-pattern host apps |\n| v2.2 | ✅ Done | Processing progress circle, Fetched page, delete confirmations, mobile nav fix |\n| v2.2.1 | ✅ Done | Security \u0026 auth hardening, default auth filter, LoginPath, SignalR/Blazor auth |\n| v2.3.0 | ✅ Done | **Operational visibility \u0026 controls** — health checks (`/healthz` + `IHealthCheck` adapter) \u0026 hero card, queue pause/resume, maintenance mode, audit log |\n| v2.3.1 | ✅ Done | Realtime analytics fixes — SQL Server `GROUP BY` (error 144) fix, fixed-cadence broadcast loop, NuGet XML docs |\n| v2.4.0 | ✅ Done | **Job Builder** — create/schedule jobs with typed arguments, guided parameter form (+ JSON), method discovery, overload-safe resolution, visual cron builder, one-off enqueue page ([#8](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/8)) |\n| v2.4.1 | ✅ Done | **Job Builder follow-up** — searchable method picker, contract-aware (interface/abstract) resolution + display names, injected-parameter edit fix ([#10](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/10)), consistent destructive-action buttons |\n| v2.5.0 | ✅ Done | **Recurring Schedule Heatmap** ([#14](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/14)) — visualize recurring-job scheduling density by queue × day × hour: Planner (cron over ad-hoc demand), Punchcard, Queue × Hour, Per-queue, Calendar, Concurrency, and stagger Recommendations; plus rollup metrics adapters (Rollup / Redis) for non-SQL storages, incl. storage-agnostic estimated durations ([#21](https://github.com/anwarminarso/a2n.Hangfire.Dashboard/issues/21)) |\n| v2.6.0 | Planned | **Integrations** — Prometheus `/metrics`, OpenTelemetry trace links, read-only REST API, CSV/JSON export |\n| v2.7.0 | Planned | **Customization** — white-label theming, show/hide built-in pages, saved views |\n| v3.0 | Planned | Stretch goals \u0026 long-term backlog (notifications \u0026 alert rules, Gantt timeline, multi-instance federation, replay, fingerprint, etc.) |\n\nSee the full [roadmap](docs/ROADMAP.md) for details.\n\n---\n\n## Contributing\n\nContributions welcome — bug reports, feature requests, documentation improvements, and pull requests.\n\n```bash\ngit clone https://github.com/anwarminarso/a2n.Hangfire.Dashboard.git\ncd a2n.Hangfire.Dashboard\ndotnet build src/Hangfire\\ Dashboard.slnx\ndotnet test\ncd samples/SampleApp \u0026\u0026 dotnet run\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for fork workflow, code style, and pull request guidelines.\n\n## License\n\nLGPL-3.0-or-later — see [LICENSE](LICENSE).\n\n## Acknowledgments\n\nThis project builds on the excellent work of the Hangfire community:\n\n- [Hangfire](https://www.hangfire.io/) — the background job framework\n- [Hangfire.Console](https://github.com/pieceofsummer/Hangfire.Console) — console output for background jobs\n- [Hangfire.Tags](https://github.com/face-it/Hangfire.Tags) — job tagging\n- [Hangfire.RecurringJobAdmin](https://github.com/bamotav/Hangfire.RecurringJobAdmin) — recurring job management UI\n- [Hangfire.Community.Dashboard.Heatmap](https://github.com/brodrigz/Hangfire.Community.Dashboard.Heatmap) by Bruno Rodrigues (brodrigz) — design inspiration for the recurring schedule heatmap (clean-room Blazor implementation, no source code copied)\n\nCommunity extensions are listed on the [Hangfire Extensions](https://www.hangfire.io/extensions.html) page. This project is community-maintained and is not officially supported by Hangfire OÜ.\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003csub\u003eBuilt with ☕ by \u003ca href=\"https://github.com/anwarminarso\"\u003eAnwar Minarso\u003c/a\u003e\u003c/sub\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanwarminarso%2Fa2n.hangfire.dashboard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanwarminarso%2Fa2n.hangfire.dashboard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanwarminarso%2Fa2n.hangfire.dashboard/lists"}