From 38f5156ca690db20bd361858323b7454f8a11333 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 28 Mar 2026 16:46:06 -0500 Subject: [PATCH] Enhancement: Show username in VOD cards. --- CHANGELOG.md | 4 +-- apps/proxy/vod_proxy/views.py | 1 + .../components/cards/VodConnectionCard.jsx | 33 +++++++++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8cab3a7..31693df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Connection cards on the Stats page now show the **username** of the connected user in a new User column (between IP Address and Connected). The username is resolved from the user store using the `user_id` stored in Redis client metadata; unauthenticated connections display "Anonymous". (Closes #766, Closes #586) -- `ip_address` and `user_id` were not included in the client info returned by `get_detailed_channel_info()` despite being available in the Redis hash. Both fields are now extracted and returned. +- Connection cards on the Stats page now show the **username** of the connected user. For live channel connections a new User column appears between IP Address and Connected; for VOD connections the username is shown inline next to the IP address in the Client summary row. The username is resolved from the user store using the `user_id` stored in Redis client metadata. (Closes #766, Closes #586) +- `ip_address` and `user_id` were not included in the client info returned by `get_detailed_channel_info()` despite being available in the Redis hash. Both fields are now extracted and returned. `user_id` is now also included in the VOD stats response. - Donate button added to the sidebar footer. A heart icon links to the project's Open Collective page, visible in both expanded and collapsed states. Hovering shows a "Support Dispatcharr" tooltip. The version string is also now clickable to copy it to the clipboard. - User stream limits: administrators can now set a maximum number of concurrent streams per user account. When a user reaches their limit, the system can automatically terminate an existing stream to free a slot based on configurable rules. Limit enforcement applies to both live channels and VOD. (Closes #544) - Each user account has a new **Stream Limit** field (0 = unlimited) configurable from the user edit form in Settings → Users. diff --git a/apps/proxy/vod_proxy/views.py b/apps/proxy/vod_proxy/views.py index d7250bcf..4382caaa 100644 --- a/apps/proxy/vod_proxy/views.py +++ b/apps/proxy/vod_proxy/views.py @@ -949,6 +949,7 @@ class VODStatsView(View): 'm3u_profile': m3u_profile_info, 'client_id': client_id, 'client_ip': combined_data.get('client_ip', 'Unknown'), + 'user_id': combined_data.get('user_id', '0'), 'user_agent': combined_data.get('client_user_agent', 'Unknown'), 'connected_at': combined_data.get('created_at'), 'last_activity': combined_data.get('last_activity'), diff --git a/frontend/src/components/cards/VodConnectionCard.jsx b/frontend/src/components/cards/VodConnectionCard.jsx index 5e081c03..90b4c034 100644 --- a/frontend/src/components/cards/VodConnectionCard.jsx +++ b/frontend/src/components/cards/VodConnectionCard.jsx @@ -1,5 +1,5 @@ // Format duration for content length -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import logo from '../../images/logo.png'; import { ActionIcon, @@ -38,6 +38,7 @@ import { getMovieDisplayTitle, getMovieSubtitle, } from '../../utils/cards/VodConnectionCardUtils.js'; +import useUsersStore from '../../store/users.jsx'; const ClientDetails = ({ connection, connectionStartTime }) => { return ( @@ -142,7 +143,10 @@ const ClientDetails = ({ connection, connectionStartTime }) => { }; const ConnectionProgress = ({ connection, durationSecs }) => { - const { totalTime, currentTime, percentage } = calculateProgress(connection, durationSecs); + const { totalTime, currentTime, percentage } = calculateProgress( + connection, + durationSecs + ); return totalTime > 0 ? ( @@ -172,6 +176,14 @@ const ConnectionProgress = ({ connection, durationSecs }) => { const VodConnectionCard = ({ vodContent, stopVODClient }) => { const { fullDateTimeFormat } = useDateTimeFormat(); const [isClientExpanded, setIsClientExpanded] = useState(false); + const users = useUsersStore((s) => s.users); + const usersMap = useMemo(() => { + const map = {}; + users.forEach((u) => { + map[String(u.id)] = u.username; + }); + return map; + }, [users]); const [, setUpdateTrigger] = useState(0); // Force re-renders for progress updates // Get metadata from the VOD content @@ -377,13 +389,12 @@ const VodConnectionCard = ({ vodContent, stopVODClient }) => { {/* Progress bar - show current position in content */} - {connection && - metadata.duration_secs && + {connection && metadata.duration_secs && ( - } + )} {/* Client information section - collapsible like channel cards */} {connection && ( @@ -403,11 +414,21 @@ const VodConnectionCard = ({ vodContent, stopVODClient }) => { > - Client: + Client IP: {connection.client_ip || 'Unknown IP'} + {usersMap[String(connection.user_id)] && ( + <> + + User: + + + {usersMap[String(connection.user_id)]} + + + )}