Skip to content

feat(tautulli): expand the Plex Activity page — stats, graphs, per-user filter & users table#62

Open
ndandan wants to merge 15 commits into
Shoshuo:mainfrom
ndandan:pr/tautulli-activity-expanded
Open

feat(tautulli): expand the Plex Activity page — stats, graphs, per-user filter & users table#62
ndandan wants to merge 15 commits into
Shoshuo:mainfrom
ndandan:pr/tautulli-activity-expanded

Conversation

@ndandan

@ndandan ndandan commented Jun 23, 2026

Copy link
Copy Markdown

Plex Activity page — statistics, graphs & a per-user filter

Builds on the optional Tautulli integration to expand the Plex Activity page. All additive, read-only, fail-open, and i18n'd (en/fr).

What's new

  • Statistics tiles: Most Popular Movies / Shows (ranked by distinct viewers) and a Most Concurrent Streams tile, plus a Play Count ⇄ Play Duration toggle that reformats every count-based tile and chart into watch time (Xh Ym).
  • Four new graphs: plays by source resolution, plays by stream resolution, streams by user, and concurrent streams over time. Concurrent is always a count and is never reformatted as a duration, even in Duration mode.
  • Users overview table: friendly name, relative last-seen, last played, play count and total watch time.
  • Page-wide per-user filter: a dropdown that scopes every section — statistics, all graphs, the users table and the history grid — to a single user, or all users.

Safety / design

  • Read-only — only get_home_stats, get_users_table, get_user_names, get_plays_by_*, get_concurrent_streams_by_stream_type. No mutating commands.
  • Privacy allow-list — email / IP / avatar / machine-id / file-path / Plex login never leave the server. The opaque Tautulli user_id is a filter token only (query param / <option value>), never rendered.
  • Validatedmetric is clamped to plays|duration; the user filter is digits-only or empty.
  • Fail open — every new client method and endpoint returns its neutral shape ([] / {categories:[],series:[]}) when Tautulli is disabled / unconfigured / unreachable; no 500s.

Verification

  • Full PHPUnit suite green; new unit tests for the normalizers (incl. privacy leak tests) and functional tests for every new endpoint (incl. invalid metric/user inputs). lint:twig + lint:yaml clean.
  • Verified live against a configured instance: metric toggle, per-user scoping, all four charts, and the users table all behave as described.

Stacking

This is stacked on #60 (pr/tautulli-activity), since it extends that page. Until #60 merges, this PR's diff includes #60's commits; once #60 lands I'll rebase so it collapses to the ~9-file stats delta. Fork-only files (docs/FORK-CHANGES.md, ghcr.yml, screenshots) are excluded as usual.

ndandan and others added 14 commits June 19, 2026 06:50
…alth chips

Adds a read-only Tautulli integration (configured in /admin/settings, behind
the per-service health circuit breaker) that surfaces current Plex activity:

- Dashboard "Current Plex activity" widget — active streams, Direct Play /
  Direct Stream / Transcode counts, total/LAN/WAN bandwidth, per-session cards
  (quality, HDR/SDR badge, source->target codec on transcode). Hydrates async
  and refreshes every 10s; fails open so a down Tautulli never breaks the page.
- Dedicated Plex Activity page (own sidebar entry): now-playing strip, watch
  stats with a 7/30/90-day toggle, plays-over-time graphs (Media/Stream-type
  toggle, by-hour, by-day-of-week, platform x stream-type problem-clients),
  dense history grid, per-library counts, and an in-app info modal per title.
- API key stays server-side; every response is sanitised before the browser.
- Chart.js self-hosted for CSP compliance, reused by the Radarr stats chart.

Also reworks the dashboard service-health chips to show latency:
HealthService::statusFor() returns a status word + round-trip ms (cached 10s,
isHealthy() now delegates to it and keeps its old contract). Chips render five
states (up / slow / very_slow / down / degraded) with a coloured dot.

Tests: TautulliClient (HTTP + normalizers + edge cases), TautulliController
(endpoints + route guard), HealthService statusFor/classifyLatency, and the
relative_date Twig filter.
Co-Authored-By: Claude Sonnet 4.6 <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.

1 participant