Healthie.NET.Dashboard 4.1.1

Prefix Reserved
dotnet add package Healthie.NET.Dashboard --version 4.1.1
                    
NuGet\Install-Package Healthie.NET.Dashboard -Version 4.1.1
                    
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="Healthie.NET.Dashboard" Version="4.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Healthie.NET.Dashboard" Version="4.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Healthie.NET.Dashboard" />
                    
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 Healthie.NET.Dashboard --version 4.1.1
                    
#r "nuget: Healthie.NET.Dashboard, 4.1.1"
                    
#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 Healthie.NET.Dashboard@4.1.1
                    
#: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=Healthie.NET.Dashboard&version=4.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Healthie.NET.Dashboard&version=4.1.1
                    
Install as a Cake Tool

Healthie.NET - Trust your uptime

Healthie.NET.Dashboard

NuGet

Zero-dependency Blazor Server dashboard for monitoring Healthie.NET pulse checkers in real time. Delivered as a Razor Class Library with pure HTML/CSS -- no third-party UI frameworks required.

Live demo — board.healthie-dotnet.dev — this dashboard, read-only, watching the real status pages of Anthropic, OpenAI, Cursor, GitHub, Cloudflare, and more. Full documentation at healthie-dotnet.dev.

The Healthie.NET dashboard

Installation

dotnet add package Healthie.NET.Dashboard

Setup

1. Register services in Program.cs:

using Healthie.Dashboard;

builder.Services.AddHealthieUI(options =>
{
    options.DashboardTitle = "System Health";
    options.EnableDarkModeToggle = true;
});

2. Add static file middleware (if not already present):

app.UseStaticFiles();

3a. Standalone endpoint (non-Blazor apps) -- map the dashboard route:

app.MapHealthieUI();                                   // Serves at /healthie/dashboard
app.MapHealthieUI().RequireAuthorization("AdminPolicy"); // With auth

3b. Embedded component (Blazor apps) -- render directly in a Razor page:

@page "/healthie/dashboard"
@using Healthie.Dashboard.Components

<HealthieDashboard />

For interactive components, the host Routes must have @rendermode="InteractiveServer" in App.razor.

Features

  • Event-driven real-time updates via IPulseChecker.StateChanged (no polling)
  • Per-checker management: start, stop, trigger, reset, retime by interval or cron expression, change threshold
  • Bulk actions: Start All, Stop All, Trigger All
  • A read-only mode that reports everything and changes nothing — see below
  • Panels for what you register — uptime, alerts, metrics, leadership, AI — see below
  • Groups and tags, both editable here and seeded from code — see below
  • A collapsible left menu: overview, a section per group with its tally, and the alerts, log and about views
  • Pin a checker to the top of the list
  • Rows or cards, sectioned by group when it opens or flat on request, with per-group tallies
  • Live event log, with a full-size view behind the expand icon
  • Legend and about behind the ? in the header
  • Dark/light theme toggle
  • Search and filter by name
  • Mobile responsive (375px+)
  • CSS-only animations, and no JavaScript of its own
  • Every time shown is UTC

Read-only mode

By default the dashboard can change what it shows: run a check, pause one, reset it, retime it, retag it. That is what it is for, and it is also more than you want to hand to everyone who can reach the URL.

builder.Services.AddHealthieUI(options => options.AllowMutations = false);

That leaves a board that only reports. Every state, sparkline, group, tag, and event stays exactly where it was; the controls that would change any of it are not rendered. Nothing is lost to the reader, because the values behind the editors are on the board already — the schedule is the row's rate, or its cron expression where it runs on one; the threshold is the denominator in FAILS; and the group and tags are the chips under each name. The side menu, searching, filtering, grouping, switching to cards, opening the event log, and the theme toggle all still work: they change your view, not the checker.

This is not authorization. It is one setting for the whole application, applied to every viewer alike, so it cannot hand the controls to an admin and withhold them from everyone else. It answers what can this board do, never who is looking — for that, gate the endpoint, which composes with it:

// Nobody unauthenticated gets in, and nobody at all gets the buttons.
builder.Services.AddHealthieUI(options => options.AllowMutations = false);
app.MapHealthieUI().RequireAuthorization();

Panels for what you register

The board grows a panel for each capability the application registers, and shows none of them otherwise. There is nothing to switch on: it renders the panel when the container can resolve the contract, so the registration is the whole configuration.

Uptime, leader election and metrics live in the core package, so those three need no extra install — just the call. Alerting and AI are separate packages, because each carries a dependency of its own.

Register What appears
AddHealthieUptime() 24H on the selected checker — uptime measured over real time — and WORST, the longest unbroken outage inside that window
Healthie.NET.Alerting An ALERTS view: the paged alert history, where each alert is delivered, and the settings a running dispatcher honours — see below
AddHealthieMetrics() A METRICS view: checks run, the share that reported healthy, transitions, overlapped triggers, and mean and slowest check duration
AddHealthieLeaderElection() A LEADER or FOLLOWER badge, hover-naming the replica — on a follower every checker sits still, which is otherwise indistinguishable from a broken board
Healthie.NET.AI An EXPLAIN button on a failing checker, which asks your IChatClient why it has been failing

The alerts view

Three things, in the order you need them.

Where alerts go. Every registered IAlertSink, listed from startup rather than from its first delivery, with its delivered and failed counts and its last error. With none configured it says so outright — alerts raised and sent nowhere reads identically to alerts being delivered, and those are opposite situations. A sink that recovers stops being shown as failing.

What has fired. The history, newest first, a page at a time, with a filter for the ones that did not reach every sink. It is written through your application's own IStateProvider, so on CosmosDB, PostgreSQL, SQL Server, SQLite or Redis it survives a redeploy; on the in-memory provider it does not. The header names the provider it went to and the cap it is kept at, so neither is a guess.

What alerting is doing. Minimum severity, deduplication window, delivery timeout and whether recoveries alert, all editable and applied from the next alert. Only those four: the dispatcher reads them on every alert, whereas its queue capacity and history length are fixed when it is built, and a control that quietly did nothing would be worse than no control. SEND TEST ALERT puts one through the real sinks — the only way to find out a webhook URL is wrong is to use it.

Settings and the test button are gated by AllowMutations; everything else in the view is a read and stays under read-only mode.

The metrics view

AddHealthieMetrics() attaches a MeterListener to the Healthie.NET meter, which is where this library already emits healthie.check.duration, .results, .transitions and .overlaps. It reads what is being published rather than instrumenting anything again, so it runs alongside an OpenTelemetry exporter on the same meter, not instead of one.

It is a live count and not a time series: nothing survives a restart, and an exporter is still the way to keep history. Opt-in for that reason — a listener costs a callback on every measurement, and an application with an APM in front of it has somewhere better to look.

OVERLAPS is the figure worth watching. A checker whose check takes longer than its own interval returns immediately, looks healthy, and is quietly running at a fraction of the rate it was asked to; this is the only place it shows.

24H sits beside the board's own UPTIME, which is the share of the runs still in the rolling history — a hundred results, so at a one-second interval, the last hundred seconds. They answer different questions and disagree for good reasons, which is why both are shown.

All of it reads and none of it writes, so it all stays under AllowMutations = false. The exception is EXPLAIN: still a read, but it spends money on your account, so it is gated with the controls that change things.

Groups and tags

The two look similar and answer different questions.

A checker belongs to one group at most. Groups are what the sectioned view splits on, so every checker appears exactly once and a section's tallies add up to what is under it.

A checker carries any number of tags. Tags describe it and filter the list, and are free to cut across groups — a tier-1 tag can sit on checkers in three different groups.

Declare the defaults in code:

public class RedisCachePulseChecker(IStateProvider stateProvider) : PulseChecker(stateProvider)
{
    public override string DisplayName => "Redis Cache";

    public override string DefaultGroup => "Data Stores";

    public override IReadOnlyList<string> DefaultTags => ["tier-1", "cache"];

    public override Task<PulseCheckerResult> CheckAsync(CancellationToken cancellationToken = default)
        => /* ... */;
}

Both can then be changed from the dashboard's side panel, and the change is stored through the IStateProvider like an interval or a threshold — so it outlives a restart. The defaults only seed a checker with no stored state; they never overwrite a change made here.

Tags are trimmed, de-duplicated case-insensitively, and ordered, so " Tier-1 " and "tier-1" are one tag. A blank group means no group, and those checkers gather under UNGROUPED.

Configuration Options

Option Type Default Description
DashboardTitle string "System Health" Title displayed at the top of the dashboard.
EnableDarkModeToggle bool true Whether the dark/light mode toggle is visible.
AllowMutations bool true Whether the controls that change a checker are rendered. false leaves a board that only reports.

See Also

Back to main README

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 was computed.  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.1.1 113 8/14/2026
4.1.0 123 7/30/2026
4.0.0 101 7/30/2026
3.1.4 103 7/19/2026
3.1.3 85 7/18/2026
3.1.2 99 7/18/2026
3.1.1 92 7/18/2026
3.1.0 111 7/17/2026
3.0.0 104 7/15/2026
2.3.0 807 2/24/2026
2.1.1-beta 117 2/24/2026
2.0.0-beta 115 2/22/2026

### Fixed

- **The dashboard cropped its own rows on anything narrower than a laptop.** The row and its column
 header share one track definition, and two rules overrode it: a read-only board drops the controls
 column, and a narrow viewport folds the row down to three. Written as separate rule sets they
 fought on specificity, and `.hpm-list--readonly .hpm-row` outranks a `.hpm-row` inside a media
 query -- so on a read-only board the fold never applied at any width. Every row was drawn with
 five tracks demanding 556px inside a 342px phone: 214px of columns clipped off the right, the name
 squeezed to its 160px minimum to make room for columns that were not visible anyway, and no
 scrollbar to reach them, because the overflow was clipped rather than scrolled. The definition is
 now a single custom property on the list, which is the one element both variants already sit on,
 so the fold cannot be outranked.
- **The last column was clipped on an ordinary 1280 laptop, read-only or not.** The detail panel
 moved below the list at 1100px, but a full row needs a 706px column and the list only reaches that
 at 1402px of viewport -- so between those two the three columns fitted the window while the middle
 one no longer fitted a row. That threshold is now 1400px, calibrated on the editable board, which
 is the wider of the two and the default.
- **Group and tag chips were sliced down the middle on a phone.** They sit on the row's sub-line,
 which truncates with `text-overflow: ellipsis` -- and that ellipsizes text, not an inline-flex
 pill, so a chip was cut mid-shape instead. The folded row has vertical room the wide one does not,
 so the line now wraps there, exactly as it already does on a card.
- **Cards lost their rate, sparkline and last-checked time on a phone.** The three cells were hidden
 for every layout below 820px, but only the row layout was short of width; a card stacks them into
 areas down its own length, where they fit. They are now hidden for the row layout alone.

### Changed

- **The packages point at [healthie-dotnet.dev](https://healthie-dotnet.dev/).** `PackageProjectUrl`
 is the landing site rather than the repository, because NuGet renders it as the package's "Project
 website" and a reader who clicks it wants documentation and the live board, not a source tree.
 `RepositoryUrl` still points at GitHub, so Source Link and NuGet's own source link are unaffected.
 Every package README now links the documentation site alongside the live demo, which moved to
 [board.healthie-dotnet.dev](https://board.healthie-dotnet.dev/).
- **Releases carry build provenance.** The release workflow attests which workflow, at which commit,
 produced each package, so `gh attestation verify <package>.nupkg --repo ivanvyd/Healthie.NET`
 proves a download came from this repository. The SBOM already said what went into a package; this
 says who built it, which is the half a tampered build would otherwise be free to restate.
- The repository README leads with a screenshot rather than a 10MB animation, and `Healthie.NET.Redis`
 gained the header every other package README already had.

Full changelog: https://github.com/ivanvyd/Healthie.NET/blob/v4.1.1/CHANGELOG.md