Repository navigation
Add Command Center widgets and API key auto-discovery (phase 2) - #50
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe dashboard adds automatic credential discovery, resilient widget results, new upstream integrations and API routes, Prometheus gauges, VPN status reporting, and three switchable web views: Command Center, Launcher, and Setup. ChangesDashboard Expansion
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Phase 2 of the dashboard: the six Command Center panels, the resource gauges, and the discovery layer that makes them work without anyone pasting an API key. Discovery is the piece that keeps the dashboard zero-config. Every service already writes its key to a file the stack bind-mounts — config.xml for the *arrs, config.ini for Tautulli, settings.json for Seerr — so the dashboard reads those through the read-only /discover mounts instead of asking. An env var still wins where one is set, for anyone running a service outside this stack. Discovery re-runs on a TTL rather than once at boot. On a clean install those files don't exist yet, so a panel has to be able to connect itself once the service behind it starts. Verified end to end: an integration went from waiting to live 33s after its config appeared, with no restart. Every widget route returns Result<T> and answers 200 even when its upstream failed, carrying a reason and a hint instead of an error status. This is what keeps one dead upstream from blanking the page. Hints are specific to the failure — a rejected Transmission credential and an unreachable Transmission need different advice, and a generic hint sends people looking in the wrong place. Two places where the design mocked data the stack can't actually prove: the VPN card drops the invented latency and reports reachability plus the provider/server already in .env, and a throughput gauge shows an unfilled ring rather than dividing by a ceiling that doesn't exist. Verified against live services: gauges match the host (20 cores, 31Gi RAM, 72T of 101T), the Sonarr calendar, Seerr requests and the merged activity feed all return real data, and no API key appears in any response body. Refs #48 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
485a831 to
85ac3df
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
dashboard/web/src/views/CommandCenter.tsx (1)
52-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winWidget payload shapes are hand-inlined here instead of imported.
Each
usePolled<Result<{...}>>()call reconstructs the server's payload shape by hand. See consolidated comment for the cross-file fix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@dashboard/web/src/views/CommandCenter.tsx` around lines 52 - 59, Update the polling declarations in CommandCenter around streams, downloads, upcoming, requests, and activity to use the shared imported payload/result types instead of hand-inlined object shapes. Reuse the established type symbols for each endpoint and preserve the existing polling intervals and usePolled behavior.dashboard/web/src/types.ts (2)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExport composite widget payload types from
types.tsinstead of inlining them at each call site.types.tsonly exports the entity-level interfaces (Stream,Download,RequestItem,UpcomingItem,ActivityItem,Gauge), not the composite shapes the server'sget*functions actually return ({ streams, count, bandwidth, transcodes },{ downloads, active },{ requests, pending },{ items: UpcomingItem[] },{ items: ActivityItem[] }).CommandCenter.tsxcompensates by re-declaring each shape inline, which will silently drift if a server payload changes.
dashboard/web/src/types.ts#L64-143: add and export named interfaces (e.g.StreamsPayload,DownloadsPayload,RequestsPayload,UpcomingPayload,ActivityPayload) mirroring the server's source modules.dashboard/web/src/views/CommandCenter.tsx#L52-59: replace each inlineResult<{...}>generic argument with the corresponding imported payload type.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@dashboard/web/src/types.ts` at line 1, Export named composite payload interfaces from types.ts for the streams, downloads, requests, upcoming, and activity response shapes, matching the server contracts. Update CommandCenter’s get* Result generics to use the corresponding imported payload types instead of inline object definitions, while leaving the existing entity interfaces and result handling unchanged.
64-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPayload types aren't exported alongside the entity types.
Only
Stream,Download,RequestItem,UpcomingItem,ActivityItem,Gaugeare exported — the composite widget payload shapes ({ streams, count, bandwidth, transcodes },{ downloads, active }, etc.) aren't, forcing consumers to re-declare them inline. See consolidated comment for the cross-file details and suggested fix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@dashboard/web/src/types.ts` around lines 64 - 143, Export named composite payload types in dashboard/web/src/types.ts for each widget response, including the stream, download, request, upcoming, activity, gauge, VPN, and integration payload shapes, using the existing entity types and Result wrapper where applicable. Keep the payload fields aligned with the server responses so consumers can import these types instead of redeclaring them inline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@dashboard/server/src/sources/seerr.ts`:
- Around line 101-131: Update load() so pending is obtained from a separate
Seerr request using filter=pending and a small take, rather than filtering the 8
most-recent results. Read the pending count from that response’s
pageInfo.results while preserving the existing request list mapping.
In `@dashboard/web/src/app/Sidebar.tsx`:
- Around line 7-13: Thread VPN loading state through Sidebar: add a vpnLoading
prop to Props, pass vpn.loading from the App.tsx Sidebar call site, and update
VpnCard’s responding/detail unavailable-state logic to require !vpnLoading
before showing RPC unreachable. Preserve the existing confirmed-unavailable
behavior once loading completes.
In `@dashboard/web/src/components/Panel.tsx`:
- Around line 82-107: Update PanelBody to accept an error prop and render the
existing error result when a transport failure occurs before the “No response
yet” fallback; preserve loading and successful-data behavior. Update every
PanelBody caller, including CommandCenter, to pass the corresponding usePolled
error state such as streams.error.
---
Nitpick comments:
In `@dashboard/web/src/types.ts`:
- Line 1: Export named composite payload interfaces from types.ts for the
streams, downloads, requests, upcoming, and activity response shapes, matching
the server contracts. Update CommandCenter’s get* Result generics to use the
corresponding imported payload types instead of inline object definitions, while
leaving the existing entity interfaces and result handling unchanged.
- Around line 64-143: Export named composite payload types in
dashboard/web/src/types.ts for each widget response, including the stream,
download, request, upcoming, activity, gauge, VPN, and integration payload
shapes, using the existing entity types and Result wrapper where applicable.
Keep the payload fields aligned with the server responses so consumers can
import these types instead of redeclaring them inline.
In `@dashboard/web/src/views/CommandCenter.tsx`:
- Around line 52-59: Update the polling declarations in CommandCenter around
streams, downloads, upcoming, requests, and activity to use the shared imported
payload/result types instead of hand-inlined object shapes. Reuse the
established type symbols for each endpoint and preserve the existing polling
intervals and usePolled behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c683d73e-400f-4d1a-99da-1050cebfe265
📒 Files selected for processing (23)
CLAUDE.mdREADME.mddashboard/README.mddashboard/server/src/config.tsdashboard/server/src/discovery.test.tsdashboard/server/src/discovery.tsdashboard/server/src/http.tsdashboard/server/src/index.tsdashboard/server/src/sources/activity.tsdashboard/server/src/sources/arr.tsdashboard/server/src/sources/prometheus.tsdashboard/server/src/sources/seerr.tsdashboard/server/src/sources/sources.test.tsdashboard/server/src/sources/tautulli.tsdashboard/server/src/sources/transmission.tsdashboard/server/src/sources/upcoming.tsdashboard/web/src/app/App.tsxdashboard/web/src/app/Sidebar.tsxdashboard/web/src/components/Gauges.tsxdashboard/web/src/components/Panel.tsxdashboard/web/src/types.tsdashboard/web/src/views/CommandCenter.tsxdashboard/web/src/views/Setup.tsx
Three fixes from review, each a case of the UI claiming more than it knew. The Seerr pending count was derived from the 8 most-recent requests, so a household with more requests than that — or with older ones still waiting behind newer arrivals — saw a count that silently capped itself. It now comes from its own filter=pending query, reading pageInfo.results, and falls back to the visible rows if that call fails so a failed count can't cost the panel its list. VpnCard treated "the first /api/vpn response hasn't arrived" as "the RPC is unreachable", so it rendered a warning on every page load before anything had actually failed. Loading is now its own state, and the warning is reserved for a confirmed problem — an icon that clears itself a moment later teaches people to stop reading the card. PanelBody reported a transport failure as "No response yet", implying a request was still in flight when it had already failed. It now takes the polling error and names it, distinct from an upstream declining to answer (which arrives as a successful response carrying `available: false`). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@dashboard/server/src/sources/seerr.ts`:
- Around line 110-116: Update pendingCount to runtime-validate
page.pageInfo?.results before returning it: accept only a finite, non-negative
number, and return fallback for missing, non-numeric, negative, or non-finite
values. Preserve the existing API request and fallback error behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c95d849b-904b-42bf-91a0-e29a6a05ad38
📒 Files selected for processing (6)
dashboard/server/src/sources/seerr.tsdashboard/server/src/sources/sources.test.tsdashboard/web/src/app/App.tsxdashboard/web/src/app/Sidebar.tsxdashboard/web/src/components/Panel.tsxdashboard/web/src/views/CommandCenter.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- dashboard/web/src/app/Sidebar.tsx
- dashboard/web/src/components/Panel.tsx
- dashboard/web/src/app/App.tsx
- dashboard/web/src/views/CommandCenter.tsx
getJson types the response body but doesn't validate it, so a successful but malformed reply — pageInfo.results as a string, a negative, a NaN — flowed straight through to the panel as the pending count. Check for a finite non-negative number and fall back to the visible rows otherwise, matching how the count already degrades on a failed request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 2 of the dashboard from #48: the six Command Center panels, the resource gauges, and the discovery layer that makes them work without anyone pasting an API key.
Based on
feat/dashboard-scaffold(#49) — review that one first; this PR retargets tomainonce it merges.Auto-discovery is what keeps this zero-config
Every service already writes its API key to a file this stack bind-mounts. The dashboard reads those through the read-only
/discovermounts rather than asking anyone to paste anything:config.xml<ApiKey>config.ini[General] api_keysettings.jsonmain.apiKeyResolution is env var → discovered file → unconfigured, so an override still works for anyone running a service outside this stack.
Discovery re-runs on a TTL rather than once at boot. On a clean install those files don't exist yet, so a panel has to connect itself once the service behind it starts.
Widgets
get_activityFailure is a first-class state
Every widget route returns
Result<T>and answers 200 even when its upstream failed, carrying areasonand ahintrather than an error status. That's what stops one dead upstream blanking the page.Hints are specific to the failure. A rejected Transmission credential and an unreachable Transmission need different advice — a generic hint sends people looking in the wrong place.
Where the design mocked data the stack can't prove
.env.Verification
Against live services, not just built:
nproc,free,df)waiting→live33s after its config file appeared, with no restartapi_enabled = 0correctly reportsblockedwith the specific fixnpm auditall cleanRefs #48
🤖 Generated with Claude Code
Summary by CodeRabbit