Files
manage/backend/src/media_library_viewer_api/integrations/jellyfin.py
T
Developer b877a32ad8 Add Jellyfin Now Playing + Grafana Panel embed widgets
Two new additive widget kinds:

Jellyfin 'now_playing': like the existing 'activity' widget but filters
to only sessions with active playback (NowPlayingItem present + not
paused). Shows who's actually watching right now. The 'activity' kind
is unchanged (shows all sessions including idle).

Grafana 'panel': embeds a single Grafana panel directly in the app via
an iframe, using Grafana's /d-solo/ endpoint (renders one panel without
dashboard chrome, kiosk=tv). Configurable dashboard_uid, panel_id, and
time range (from/to, defaults now-1h/now). Includes a fallback 'Open in
Grafana' link for when embedding is blocked by X-Frame-Options/CSP. The
'link' kind is unchanged (still builds a deep-link URL).

Backend: new widget configs + definitions on jellyfin/grafana; source
adapter logic (session filter for now_playing; d-solo embed URL for
panel); 6 new tests.

Frontend: JellyfinNowPlayingWidget + GrafanaPanelWidget components;
registry bindings; 6 new tests.

278 backend tests pass (+6); 127 frontend tests pass (+6); lint/build
green both sides.
2026-06-28 15:26:43 +00:00

72 lines
1.9 KiB
Python

"""Jellyfin service definition."""
from __future__ import annotations
from media_library_viewer_api.integrations.base import (
SecretField,
ServiceBaseUrl,
ServiceConfigBase,
ServiceDefinition,
WidgetConfigBase,
widget_kind,
)
class JellyfinConfig(ServiceConfigBase):
"""Non-secret Jellyfin connection config.
The optional ``jellyseerr_url`` / ``jellyseerr_api_key`` fields carry the
paired Jellyseerr companion config, absorbed from the former standalone
``jellyseerr`` service type (see OpenSpec change ``services-as-hub-ia``).
When both are set, the Jellyfin service page renders a Requests tab backed
by Jellyseerr.
"""
base_url: ServiceBaseUrl
user_id: str = ""
timeout_seconds: int = 10
jellyseerr_url: str = ""
jellyseerr_api_key: str = ""
class JellyfinActivityWidgetConfig(WidgetConfigBase):
"""Live Jellyfin session activity."""
# No user-overridable fields; the service record carries user_id.
pass
class JellyfinNowPlayingWidgetConfig(WidgetConfigBase):
"""Only show sessions with active playback (not idle/paused)."""
pass
DEFINITION = ServiceDefinition(
service_type="jellyfin",
name="Jellyfin",
description="Media server with live session activity.",
config_model=JellyfinConfig,
secret_fields=[
SecretField(key="api_key", label="API key", required=True),
],
widget_kinds=[
widget_kind(
kind="activity",
name="Activity",
description="Live sessions and idle users.",
model_cls=JellyfinActivityWidgetConfig,
default_config={},
refresh_interval_ms=30_000,
),
widget_kind(
kind="now_playing",
name="Now Playing",
description="Only sessions actively playing media.",
model_cls=JellyfinNowPlayingWidgetConfig,
default_config={},
refresh_interval_ms=30_000,
),
],
)