Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Fixed

- The dashboard event-log and about dialogs now move focus inside, keep keyboard focus contained,
close with Escape, and restore focus to the control that opened them.
- Dashboard checker rows now expose selection as a real button alongside their action buttons,
removing nested interactive controls while preserving whole-row pointer selection.
- A dashboard state-provider failure now shows a recoverable unavailable state with a retry action
instead of looking like an empty installation with no registered checkers.
- The Blazor sample now keeps the dashboard stylesheet in its static host head, preventing an
unstyled flash while the interactive head reconnects on a slow network.
- HTTP checker display names and result messages now omit URI user information, query strings, and
fragments, server-controlled reason phrases, and transport exception details, preventing embedded
credentials and tokens from reaching stored history, alerts, logs, or the dashboard while requests
Expand Down
3 changes: 3 additions & 0 deletions samples/Healthie.Sample.BlazorUI/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
reset that stops a pale frame being drawn around the dashboard -- did nothing. *@
<link href="app.css" rel="stylesheet" />
<link href="Healthie.Sample.BlazorUI.styles.css" rel="stylesheet" />
@* Keep the dashboard styled while the interactive HeadOutlet reconnects on a slow network. *@
<link href="_content/Healthie.NET.Dashboard/healthie.css" rel="stylesheet"
data-healthie-static-styles />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet @rendermode="InteractiveServer" />
</head>
Expand Down
55 changes: 33 additions & 22 deletions src/Healthie.Dashboard/Components/HealthieDashboard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@inject HealthieThemeState ThemeState
@inject PersistentComponentState PersistedState
@inject DashboardStateHandoff Handoff
@inject IJSRuntime JS

<PageTitle>@Options.DashboardTitle</PageTitle>
<HeadContent>
Expand Down Expand Up @@ -96,7 +97,7 @@
<button type="button" class="hpm-btn hpm-btn--icon @(_showAbout ? "hpm-btn--on" : "")"
title="What the colours and controls mean" aria-label="Legend and about"
aria-expanded="@(_showAbout ? "true" : "false")"
@onclick="ToggleAbout">?</button>
@onclick="ToggleAboutAsync">?</button>
</div>
</div>

Expand Down Expand Up @@ -143,16 +144,17 @@
*@
@if (_showLog)
{
<div class="hpm-backdrop" @onclick="ToggleLog" aria-hidden="true"></div>
<div class="hpm-backdrop" @onclick="CloseLogAsync" aria-hidden="true"></div>

<div class="hpm-modal" role="dialog" aria-modal="true" aria-label="Event log">
<div class="hpm-modal" role="dialog" aria-modal="true" aria-labelledby="hpm-event-log-title"
tabindex="-1" @ref="_logDialog" @onkeydown="OnLogDialogKeyDown">
<div class="hpm-popover-head">
<span class="hpm-sel-name">Event log</span>
<span id="hpm-event-log-title" class="hpm-sel-name">Event log</span>
<span class="hpm-tag">@_events.Count</span>
<span class="hpm-modal-note">newest first · UTC</span>
<div class="hpm-spacer"></div>
<button type="button" class="hpm-icon-btn" title="Close" aria-label="Close"
@onclick="ToggleLog">@HealthieIcons.X</button>
@onclick="CloseLogAsync">@HealthieIcons.X</button>
</div>

<div class="hpm-modal-body">
Expand Down Expand Up @@ -194,15 +196,16 @@
*@
@if (_showAbout)
{
<div class="hpm-backdrop" @onclick="ToggleAbout" aria-hidden="true"></div>
<div class="hpm-backdrop" @onclick="CloseAboutAsync" aria-hidden="true"></div>

<div class="hpm-popover" role="dialog" aria-modal="true" aria-label="Legend and about">
<div class="hpm-popover" role="dialog" aria-modal="true" aria-label="Legend and about"
tabindex="-1" @ref="_aboutDialog" @onkeydown="OnAboutDialogKeyDown">
<div class="hpm-popover-head">
<span class="hpm-sel-name">Healthie<span class="hpm-dot">·</span>NET</span>
<span id="hpm-about-title" class="hpm-sel-name">Healthie<span class="hpm-dot">·</span>NET</span>
<span class="hpm-tag">v@(PackageVersion)</span>
<div class="hpm-spacer"></div>
<button type="button" class="hpm-icon-btn" title="Close" aria-label="Close"
@onclick="ToggleAbout">@HealthieIcons.X</button>
@onclick="CloseAboutAsync">@HealthieIcons.X</button>
</div>

<div class="hpm-popover-body">
Expand Down Expand Up @@ -265,23 +268,23 @@
var history = HistoryOf(name);
}
<div class="hpm-row @StatusClass(status) @(isSelected ? "hpm-row--selected" : "") @(state.IsActive ? "" : "hpm-row--paused")"
role="button" tabindex="0"
aria-pressed="@(isSelected ? "true" : "false")"
@onclick="@(() => SelectAsync(name))"
@onkeydown="@(e => OnRowKeyDown(e, name))">
@onclick="@(() => SelectAsync(name))">

<span class="hpm-led @(state.IsActive ? "" : "hpm-led--steady") @(status == Status.Critical ? "hpm-led--urgent" : "")"
aria-hidden="true"></span>

<div class="hpm-row-name">
<div class="hpm-row-title">
<button type="button" class="hpm-row-name hpm-row-select"
aria-label="View @DisplayNameOf(name) details"
aria-pressed="@(isSelected ? "true" : "false")"
@onclick="@(() => SelectAsync(name))" @onclick:stopPropagation="true">
<span class="hpm-row-title">
@if (state.IsPinned)
{
<span class="hpm-pin-mark" title="Pinned to the top" aria-label="Pinned">@HealthieIcons.Pin</span>
}
@DisplayNameOf(name)
</div>
<div class="hpm-row-sub">
</span>
<span class="hpm-row-sub">
@StatusWord(state) · @ShortTypeName(name)
@if (!string.IsNullOrWhiteSpace(state.Group))
{
Expand All @@ -291,8 +294,8 @@
{
<span class="hpm-chip" title="Tagged @tag">@tag</span>
}
</div>
</div>
</span>
</button>

<div class="hpm-rate-cell">
<span class="hpm-rate @(state.IsActive ? "" : "hpm-rate--paused") @(state.EffectiveSchedule.IsCron ? "hpm-rate--cron" : "")"
Expand Down Expand Up @@ -422,7 +425,7 @@
<button type="button" class="hpm-nav-item @(_showLog ? "hpm-nav-item--current" : "")"
aria-expanded="@(_showLog ? "true" : "false")"
title="Every state change this dashboard has watched happen"
@onclick="ToggleLog">
@onclick="ToggleLogAsync">
<span class="hpm-nav-dot hpm-nav-dot--log" aria-hidden="true"></span>
<span class="hpm-nav-text">EVENT LOG</span>
<span class="hpm-nav-count">@_events.Count</span>
Expand All @@ -431,7 +434,7 @@
<button type="button" class="hpm-nav-item @(_showAbout ? "hpm-nav-item--current" : "")"
aria-expanded="@(_showAbout ? "true" : "false")"
title="What the colours and controls mean"
@onclick="ToggleAbout">
@onclick="ToggleAboutAsync">
<span class="hpm-nav-dot hpm-nav-dot--about" aria-hidden="true"></span>
<span class="hpm-nav-text">ABOUT</span>
</button>
Expand Down Expand Up @@ -737,6 +740,14 @@
<div class="hpm-skeleton"></div>
}
}
else if (_loadError is not null)
{
<div class="hpm-load-error" role="alert">
<strong>CHECKER DATA UNAVAILABLE</strong>
<span>@_loadError</span>
<button type="button" class="hpm-btn hpm-btn--sm" @onclick="RetryLoadAsync">TRY AGAIN</button>
</div>
}
else if (_filtered.Count == 0)
{
<div class="hpm-empty">
Expand Down Expand Up @@ -990,7 +1001,7 @@
<span class="hpm-log-led" aria-hidden="true"></span>
<button type="button" class="hpm-icon-btn hpm-log-expand"
title="Open the full event log" aria-label="Expand the event log"
@onclick="ToggleLog">@HealthieIcons.Expand</button>
@onclick="ToggleLogAsync">@HealthieIcons.Expand</button>
</div>
<div class="hpm-log-body">
@if (_events.Count == 0)
Expand Down
Loading
Loading