Skip to content

Add a standalone web UI for search, download and monitoring - #44

Merged
ChrisonSimtian merged 6 commits into
mainfrom
feat/standalone-ui
Jul 26, 2026
Merged

Add a standalone web UI for search, download and monitoring#44
ChrisonSimtian merged 6 commits into
mainfrom
feat/standalone-ui

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Contributor

What & why

A fresh, purpose-built first-party UI so Krautwatch is usable on its own — no Sonarr/Radarr required. (The pre-DR-010 browser was deleted in #39; this is a clean rebuild, not that.)

Presentation/WebBlazor Server, talks to the Application layer in-process (no HTTP hop, per the DR-010 realization). Three pages:

  • Home — what it is + jump-off.
  • Search — find crawled episodes across ARD/ZDF/KiKA and download one with a click (via AddDownloadByTokenHandler — the same enqueue the SABnzbd side uses).
  • Activity — a live download monitor, auto-refreshing every 3s off GetDownloadQueue, with a progress bar (now that Report live download progress and reclaim jobs after a crash #42 persists live %).

Each user action uses a fresh DI scope, so Blazor Server's circuit-scoped DbContext is never used concurrently (the standard EF-on-Blazor-Server pitfall). AppHost runs it (WithExternalHttpEndpoints, WaitForCompletion(migrator)); it does not own migrations.

Bonus: fixes a latent fleet-wide DI bug

Bringing up the Web surfaced that AddApplication registered RunDownloadHandler (needs IDownloadProvider) and AddInfrastructure registered IMessageDispatcher→WolverineDispatcher (needs Wolverine's IMessageBus) for all hosts — so dev-time ValidateOnBuild would fail in any host lacking those deps (Web, newznab, migrator, crawl agents). Scoped both to their real consumers:

  • RunDownloadHandler → registered in the Downloader host only.
  • IMessageDispatcher → new AddMessageDispatcher(), called by the crawl agents (which have Wolverine).

Verification

Smoke-tested the app: it boots, /, /health and /app.css all serve 200, Home renders (SSR). ./build.cmd Test green (Application 47 / Architecture 8 / Infrastructure 58). Full interactive search/download/monitor needs the Aspire fleet + Postgres (a real run), but the wiring + startup are verified.

Scope

  • This is the standalone operator UI. The *arr config surface (Sonarr/Radarr instances, apikey management) is still separate future work.
  • No auth on the UI yet (assumes trusted LAN / behind a reverse proxy) — worth noting for exposure.

Remaining polish after this: Sonarr reach-back / watchlist, crawler HLS fallback, distribution (Aspire compose), geo-issue #41.

🤖 Generated with Claude Code

ChrisonSimtian and others added 6 commits July 27, 2026 00:08
A fresh, purpose-built first-party UI so Krautwatch is usable on its own, without
a Sonarr/Radarr instance (the old pre-DR-010 browser was deleted in #39; this is
not that).

- Presentation/Web: Blazor Server, talks to the Application layer in-process (no
  HTTP hop). Pages: Home, Search (find crawled episodes → one-click download via
  AddDownloadByTokenHandler), Activity (live download monitor, auto-refresh every
  3s off GetDownloadQueue). Each user action uses a fresh DI scope so Blazor
  Server's circuit-scoped DbContext can't be used concurrently.
- AppHost runs it (WithExternalHttpEndpoints, WaitForCompletion(migrator)); it does
  not own migrations. slnx + AppHost refs updated.

Also fixes a latent fleet-wide DI bug this surfaced: AddApplication registered
RunDownloadHandler (needs IDownloadProvider) and AddInfrastructure registered
IMessageDispatcher→WolverineDispatcher (needs Wolverine's IMessageBus) for ALL
hosts, so dev-time ValidateOnBuild failed in any host without those deps (Web,
newznab, migrator, crawl agents). Scoped both to their real consumers:
RunDownloadHandler → the Downloader host; IMessageDispatcher → new
AddMessageDispatcher() called by the crawl agents.

Smoke-tested: the app boots, Home/health/app.css serve 200. ./build.cmd Test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Running the fleet for the first time surfaced this: the hand-created projects have
no launch profile, so Aspire found no http endpoint and WithHttpHealthCheck /
WithExternalHttpEndpoints crashed at startup ("no endpoint was found matching
https, http" on newznab). Add WithHttpEndpoint() to each project so the endpoints
exist. Verified: full fleet boots — postgres + migrator + newznab + web + 3 agents,
agents crawl real episodes, web serves the UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two bugs found running the UI live:
- Interactivity was dead: /_framework/blazor.web.js 404'd because the app used
  UseStaticFiles(), but .NET 10 serves the Blazor framework assets via
  MapStaticAssets(). Swapped it — blazor.web.js now 200 and the circuit connects.
- Search showed "no stream" for every result (so no Download button):
  EpisodeRepository.SearchAsync didn't Include(Streams). Added it.

Verified standalone: home/blazor.web.js/app.css all 200, circuit negotiates,
search returns episodes with streams from a live catalog (81 crawled episodes).

Note: through Aspire's *dev* proxy, MapStaticAssets' blazor.web.js 500s (an
Aspire-proxy quirk, not the app) — direct and behind a real reverse proxy it's 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…downloads)

The seeded default download dir is "/downloads" (a mounted volume in a container),
but running the fleet as bare processes it's a read-only path at filesystem root —
every download failed with "Read-only file system : '/downloads'".

- The Downloader now applies a Download:Directory config value over the seeded
  AppSettings default on startup, so deployments can point downloads anywhere
  writable (a container still gets /downloads via the same setting).
- AppHost points the dev fleet's Downloader at a writable temp dir automatically,
  so `dotnet run` on the AppHost just works.

Verified live on the full fleet: search → download → a real 1.1 GB Extra 3 MP4
landed at <dir>/ARD/<show>/<show> - <title> (date).mp4, live progress % ticking
the whole way. ./build.cmd Test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Failed downloads happen (geo-blocks, network, expired URLs), so the operator needs
to act on them. Added per-job icon buttons on Activity:
- Retry (↻) on failed jobs — re-queues a fresh attempt (existing RetryDownloadHandler)
  and drops the old failed row so the list stays tidy.
- Remove (🗑) on any terminal job — deletes it from queue/history (file left on disk).

Backend: IDownloadJobRepository.DeleteAsync (ExecuteDelete) + DeleteDownloadHandler.
Icons are inline SVG (Feather refresh-cw / trash) styled with the accent, hover-danger
on remove. Active jobs show no actions (in flight).

Tests: repo Delete removes the job. ./build.cmd Test green (Infra 59).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…resume)

Active jobs had no controls. Added a Cancel (✕) action on Queued and Downloading
jobs. Since the UI and Downloader are separate processes, cancel is a DB signal:

- Cancel marks the job Cancelled (existing CancelDownloadHandler). A Queued job is
  then never claimed.
- For a Downloading job, the Downloader now polls the job's status each progress
  tick (~5s) and, on seeing Cancelled, aborts via a linked CancellationTokenSource;
  the provider deletes its partial .part/.mp4, and the job is left Cancelled
  (then Retry/Remove apply).
- Progress is now persisted with a targeted ExecuteUpdate (ProgressPercent only) so
  an out-of-band cancel isn't clobbered by the worker's writes.

Pause/resume deliberately NOT built: HTTP range-resume + partial bookkeeping is
high-cost and impossible for the ffmpeg/HLS remux path, for downloads that run a
couple of minutes — Cancel + Retry covers the need.

Tests: repo Delete + existing handler/repo suites green (App 47 / Arch 8 / Infra 59).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ChrisonSimtian
ChrisonSimtian merged commit 17d5508 into main Jul 26, 2026
1 check passed
@ChrisonSimtian
ChrisonSimtian deleted the feat/standalone-ui branch July 26, 2026 13:30
@ChrisonSimtian ChrisonSimtian added the enhancement New feature or request label Aug 2, 2026
@ChrisonSimtian ChrisonSimtian changed the title feat(web): standalone Blazor UI — search / download / monitor Add a standalone web UI for search, download and monitoring Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant