Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to Prismarr are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Plex activity via Tautulli (optional).** A new read-only Tautulli integration (URL + API key in `/admin/settings`, behind the per-service health circuit breaker) surfaces current Plex activity. The dashboard gets a "Current Plex activity" widget — active streams, Direct Play / Direct Stream / Transcode counts, total / LAN / WAN bandwidth and a per-session card (quality, HDR/SDR badge, source→target codec when transcoding) — that hydrates on its own and refreshes every 10 s. A dedicated **Plex Activity** page (own sidebar entry) adds a now-playing strip, watch statistics with a 7 / 30 / 90-day toggle (top movies / shows / users / platforms), plays-over-time graphs with a Media-type ⇄ Stream-type toggle plus by-hour and by-day-of-week breakdowns and a platform × stream-type "problem clients" chart, a dense watch-history grid, and per-library item counts. Each title opens an in-app info modal (synopsis, ratings, cast/crew). The API key never leaves the server, every response is sanitised before it reaches the browser, and each section fails open independently so a down/misconfigured Tautulli never breaks the dashboard or page. Chart.js is self-hosted (`public/static/chart/`) for CSP compliance and reused by the existing Radarr stats chart.
- **In-place quick-look on the dashboard.** Clicking any media tile — hero spotlight, upcoming, weekly trending, watchlist or latest additions — now opens a read-only detail modal right on the dashboard (poster, year, status, rating, genres, synopsis) instead of navigating away, with a deep-link to manage the item in Radarr/Sonarr or open it in Discover. Library items resolve from the already-cached dashboard aggregate (zero extra upstream calls on a warm page, falling back to a direct fetch on a miss); TMDb items resolve straight from TMDb. The fragment is read-only and fails open to a small graceful body, and the modal is Turbo-safe (declarative trigger, escaped values).

### Changed
- **Dashboard service-health chips show latency.** `HealthService::statusFor()` returns a status word plus a round-trip reading (cached 10 s like the existing bool path, which now delegates to it); the dashboard chips render five states — up / slow / very_slow / down / degraded — with a coloured dot, so a reachable-but-slow service is visibly distinct from a healthy one. `isHealthy()` keeps its old contract for every existing caller.

## [1.1.1] - 2026-06-10

### Fixed
Expand Down
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,34 @@ a request UI (Seerr).
- Hero spotlight with a random pick from your library
- Upcoming releases (seven-day mini-calendar)
- Pending Seerr requests enriched with TMDb metadata
- Live health of all six services
- Live health of all configured services
- Personal watchlist, weekly TMDb trending, latest library additions
- **Quick-look in place:** click any tile (hero, upcoming, trending, watchlist,
latest additions) to open a read-only detail modal right on the dashboard —
poster, status, rating, genres and synopsis — with a deep-link to manage it in
Radarr/Sonarr or open it in Discover, instead of navigating away
- Optional **Current Plex activity** widget via the Tautulli API: active
streams, Direct Play / Direct Stream / Transcode counts, LAN/WAN
bandwidth and a per-session card (read-only, refreshes every 10s)
- Near-instant load: the page paints first, then each widget hydrates on its own

### Plex activity (Tautulli)
- Dedicated **Plex Activity** page (its own sidebar entry) backed by a
read-only Tautulli connection — the API key never leaves the server and
every response is sanitised before it reaches the browser
- Live "Now playing": a stream-summary strip (session count, Direct Play /
Direct Stream / Transcode breakdown, total / LAN / WAN bandwidth) plus a card
per session showing quality, an HDR/SDR badge and, when transcoding, the
source→target codec
- Watch statistics with a 7 / 30 / 90-day toggle: most-watched movies and
shows, most-active users, top platforms
- Graphs: plays over time with a Media-type ⇄ Stream-type toggle, plays by hour
of day and by day of week, and a platform × stream-type "problem clients"
chart
- A dense watch-history grid and a per-library item / episode count
- Every title opens the in-app info modal (synopsis, ratings, cast/crew); each
section fails open independently, so a down Tautulli never breaks the page

### Downloads
- Full qBittorrent dashboard: server-side pagination, sorting and filters
- Drag-and-drop `.torrent` upload (multi-file)
Expand Down Expand Up @@ -206,6 +230,7 @@ a request UI (Seerr).
- At least one of: qBittorrent, Radarr, Sonarr, Prowlarr, Seerr
- Optional: Gluetun if qBittorrent runs behind a VPN
- Optional: a TMDb API key (free) to enable the Discovery page
- Optional: a Tautulli instance (URL + API key) for the Current Plex activity widget

### Install

Expand Down
4 changes: 4 additions & 0 deletions symfony/public/img/services/ATTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ jellyseerr, qbittorrent, sabnzbd, nzbget, gluetun, tmdb) come from the
- Source: https://github.com/homarr-labs/dashboard-icons
- License: Apache License 2.0

`tautulli.svg` is an original, simplified mark drawn for Prismarr in
Tautulli's brand amber (`#e5a00d`); it is not taken from the Dashboard
Icons set.

Each logo remains a trademark of its respective project. They are used
here nominatively, to identify the third-party services Prismarr can
connect to, never to imply endorsement.
1 change: 1 addition & 0 deletions symfony/public/img/services/tautulli.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions symfony/public/static/chart/chart.umd.min.js

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion symfony/src/Controller/AdminSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class AdminSettingsController extends AbstractController
['key' => 'gluetun_api_key', 'type' => 'password', 'label' => 'admin.field.api_key_if_protected'],
['key' => 'gluetun_protocol', 'type' => 'text', 'label' => 'admin.field.protocol'],
],
'tautulli' => [
['key' => 'tautulli_url', 'type' => 'text', 'label' => 'admin.field.url', 'placeholder' => 'http://host.docker.internal:8181'],
['key' => 'tautulli_api_key', 'type' => 'password', 'label' => 'admin.field.api_key'],
],
];

/**
Expand Down Expand Up @@ -128,6 +132,7 @@ class AdminSettingsController extends AbstractController
'sabnzbd' => 'SABnzbd',
'nzbget' => 'NZBGet',
'gluetun' => 'Gluetun',
'tautulli' => 'Tautulli',
];

/**
Expand Down Expand Up @@ -474,6 +479,7 @@ public function test(string $service, Request $request): JsonResponse
'qbittorrent' => ['qbittorrent_url', 'qbittorrent_user', 'qbittorrent_password'],
'sabnzbd' => ['sabnzbd_url', 'sabnzbd_api_key'],
'nzbget' => ['nzbget_url', 'nzbget_user', 'nzbget_password'],
'tautulli' => ['tautulli_url', 'tautulli_api_key'],
default => [],
};
$overrides = [];
Expand Down Expand Up @@ -522,7 +528,7 @@ public function test(string $service, Request $request): JsonResponse
public function healthInvalidate(string $service): JsonResponse
{
$service = strtolower($service);
$allowed = ['radarr', 'sonarr', 'prowlarr', 'jellyseerr', 'qbittorrent', 'tmdb', 'sabnzbd', 'nzbget'];
$allowed = ['radarr', 'sonarr', 'prowlarr', 'jellyseerr', 'qbittorrent', 'tmdb', 'sabnzbd', 'nzbget', 'tautulli'];
if (!in_array($service, $allowed, true)) {
return new JsonResponse(['ok' => false], 400);
}
Expand Down
Loading