Add a standalone web UI for search, download and monitoring - #44
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/Web— Blazor Server, talks to the Application layer in-process (no HTTP hop, per the DR-010 realization). Three pages:AddDownloadByTokenHandler— the same enqueue the SABnzbd side uses).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
DbContextis 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
AddApplicationregisteredRunDownloadHandler(needsIDownloadProvider) andAddInfrastructureregisteredIMessageDispatcher→WolverineDispatcher(needs Wolverine'sIMessageBus) for all hosts — so dev-timeValidateOnBuildwould 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→ newAddMessageDispatcher(), called by the crawl agents (which have Wolverine).Verification
Smoke-tested the app: it boots,
/,/healthand/app.cssall serve 200, Home renders (SSR)../build.cmd Testgreen (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
Remaining polish after this: Sonarr reach-back / watchlist, crawler HLS fallback, distribution (Aspire compose), geo-issue #41.
🤖 Generated with Claude Code