Skip to content

Add web UI dashboard with health status, #90 - #93

Open
paulirwin wants to merge 2 commits into
mainfrom
issue/90
Open

Add web UI dashboard with health status, #90#93
paulirwin wants to merge 2 commits into
mainfrom
issue/90

Conversation

@paulirwin

Copy link
Copy Markdown
Member

Closes #90 (first sub-issue of #68).

Adds a web UI at the emulator's root URL showing basic health status, plus the health-check infrastructure behind it.

What's here

The dashboard — served at /, showing overall status, version, environment, uptime, the indexes directory, and each check's result. It refreshes itself every 15 seconds over the Blazor circuit and has a manual refresh button.

Blazor, per the issue, so there's no separate JS framework to build or keep in sync — the whole UI lives in the same assembly. It's written from scratch rather than from the Blazor template: the template brings bootstrap, a sample stylesheet and the default error UI along for what is a handful of cards. The stylesheet is ~340 lines of hand-written CSS with tokens for light and dark, following the developer's OS theme.

Two health checks, surfaced on the dashboard and at /health:

  • index-storage — can the indexes directory actually be written to? This is the one worth having. A read-only Docker volume, or a dotnet tool launched from a directory the user can't write to, currently surfaces as a 500 from whichever indexing request happens to run. Writability is probed with a real write, because no file attribute distinguishes a read-only bind mount from a full disk from a POSIX mode bit.
  • index-definitions — does every definition on disk still deserialize? These are JSON files users are told about and mount as a volume, so hand-editing one is expected. A malformed file currently takes out an unrelated listing request. Reports Degraded rather than Unhealthy, since every index that does parse is still being served.

/health is kept separate from /servicestats, which Aspire already probes — that route imitates Azure's API surface and answers 200 as long as the process is up.

Two accommodations for owning /

Both are the kind of change that works locally and breaks the API surface elsewhere, so the integration tests assert the API side in the same file.

  • OData's service document gave up the root. AddRouteComponents("", model) puts MetadataController.GetServiceDocument on /, which made the dashboard's @page "/" an ambiguous match — the request failed with AmbiguousMatchException rather than either endpoint winning. The service document is the one to drop: Azure AI Search doesn't serve one (its root 404s) and the Azure SDK never requests it. /$metadata is a separate action on the same controller and is unaffected — there's a test for that.
  • UseAntiforgery is now in the pipeline, required by MapRazorComponents. It ignores requests that carry no token, so unauthenticated SDK writes are unaffected — ApiWrites_AreNotBlockedByAntiforgery drives a real index create/read/delete through the SDK to pin it.

Testing

  • 8 unit tests over the health checks, against real directories (the conditions they exist to catch are filesystem states; a mocked filesystem would only test the mock's opinion of a failed write). Covers healthy, not-yet-created, unreachable, non-writable, no-probe-file-left-behind, and the malformed-definition path.
  • 5 integration tests against the containerized emulator: the dashboard renders with prerendered status in the markup, the stylesheet serves, /health answers, $metadata survives, and SDK writes aren't blocked.
  • Full suite green: 981 unit + 225 integration + 7 Aspire, no regressions.

Verified by hand against a running instance as well — root, /health, /servicestats, /indexes, /$metadata, index create, document index, and delete all behave as before.

README gains a "Web UI" section covering the dashboard, /health, and the service-document note.

🤖 Generated with Claude Code

paulirwin and others added 2 commits September 1, 2026 12:56
Serves a Blazor dashboard at the emulator's root URL showing whether it is
running correctly, backed by two health checks also exposed at /health.

Blazor rather than a separate JS framework so the UI ships inside the same
assembly — no second build step and nothing extra for `dotnet tool install`
to pull down. The page is written from scratch rather than from the Blazor
template, which would have brought bootstrap and a sample stylesheet along
for what is a handful of cards.

The checks cover what actually goes wrong in the environment rather than in
the emulator: whether the indexes directory can be written to (a read-only
Docker volume, a tool launched from a directory without permission), and
whether every index definition on disk still parses after being hand-edited.
Both previously surfaced as a 500 from whichever request happened to touch
them. Writability is probed with a real write, since no file attribute
distinguishes a read-only bind mount from a full disk.

Two accommodations were needed to put a UI at "/". OData's route components
are registered at the root prefix, which puts its service document on "/" and
made the dashboard's page an ambiguous match; the service document gives way,
as Azure AI Search does not serve one and the SDK never requests it.
MapRazorComponents also requires UseAntiforgery, which is added ahead of the
API routes — it ignores requests carrying no token, so SDK writes are
unaffected, and an integration test pins that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodeQL has failed on every run since the upgrade to Aspire 13.5.3, on main as
well as on branches, and for two stacked reasons.

The visible one was ASPIRE009: autobuild builds
AzureSearchEmulator.Aspire.DemoAppHost, whose AppHost SDK resolves an Aspire
CLI bundle during build and errors out when it cannot find one, after which
autobuild gives up with "Could not auto-detect a suitable build method".

Building the solution explicitly gets past that, but only far enough to reach
the real blocker: the repository has CodeQL default setup enabled, covering
csharp and actions since 2026-08-10, and GitHub refuses SARIF from an advanced
configuration while it is on — "CodeQL analyses from advanced configurations
cannot be processed when the default setup is enabled". The two are mutually
exclusive, so the checked-in workflow could not have succeeded regardless of
how it built.

Default setup already scans this repository, so the workflow file is redundant
and is removed rather than repaired.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web UI infrastructure + health status

1 participant