mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-19 01:25:06 +00:00
Enhancement: client_connect and client_disconnect system events now include the **username** of the connected user. The username is stored alongside the client metadata in Redis and included in the event payload for log_system_event calls (making it available to webhook and script integrations).
This commit is contained in:
parent
1d3cd5fd75
commit
6d06422ddd
4 changed files with 19 additions and 9 deletions
|
|
@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- 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.
|
||||
- Web UI stream preview now sends an `Authorization: Bearer` header with each mpegts.js request, identifying the logged-in user. Live channel previews initiated from the web UI now appear on the Stats page with the correct username rather than as unknown user.
|
||||
- `client_connect` and `client_disconnect` system events now include the **username** of the connected user. The username is stored alongside the client metadata in Redis and included in the event payload for `log_system_event` calls (making it available to webhook and script integrations).
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -278,7 +278,8 @@ class ClientManager:
|
|||
"channel_id": self.channel_id,
|
||||
"client_id": client_id,
|
||||
"worker_id": self.worker_id or "unknown",
|
||||
"timestamp": time.time()
|
||||
"timestamp": time.time(),
|
||||
"username": user.username if user is not None else "unknown"
|
||||
}
|
||||
|
||||
if user_agent:
|
||||
|
|
@ -319,8 +320,11 @@ class ClientManager:
|
|||
self.last_active_time = time.time()
|
||||
|
||||
if self.redis_client:
|
||||
# Get client IP before removing the data
|
||||
# Get client data before removing the data
|
||||
client_key = f"ts_proxy:channel:{self.channel_id}:clients:{client_id}"
|
||||
client_username = self.redis_client.hget(client_key, "username") or "unknown"
|
||||
if isinstance(client_username, bytes):
|
||||
client_username = client_username.decode("utf-8")
|
||||
|
||||
# Remove from channel's client set
|
||||
self.redis_client.srem(self.client_set_key, client_id)
|
||||
|
|
@ -361,7 +365,8 @@ class ClientManager:
|
|||
"client_id": client_id,
|
||||
"worker_id": self.worker_id or "unknown",
|
||||
"timestamp": time.time(),
|
||||
"remaining_clients": remaining
|
||||
"remaining_clients": remaining,
|
||||
"username": client_username
|
||||
})
|
||||
self.redis_client.publish(RedisKeys.events_channel(self.channel_id), event_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class StreamGenerator:
|
|||
data delivery, and cleanup.
|
||||
"""
|
||||
|
||||
def __init__(self, channel_id, client_id, client_ip, client_user_agent, channel_initializing=False):
|
||||
def __init__(self, channel_id, client_id, client_ip, client_user_agent, channel_initializing=False, user=None):
|
||||
"""
|
||||
Initialize the stream generator with client and channel details.
|
||||
|
||||
|
|
@ -35,12 +35,14 @@ class StreamGenerator:
|
|||
client_ip: Client's IP address
|
||||
client_user_agent: User agent string from client
|
||||
channel_initializing: Whether the channel is still initializing
|
||||
user: Authenticated user making the request
|
||||
"""
|
||||
self.channel_id = channel_id
|
||||
self.client_id = client_id
|
||||
self.client_ip = client_ip
|
||||
self.client_user_agent = client_user_agent
|
||||
self.channel_initializing = channel_initializing
|
||||
self.user = user
|
||||
|
||||
# Performance and state tracking
|
||||
self.stream_start_time = time.time()
|
||||
|
|
@ -112,7 +114,8 @@ class StreamGenerator:
|
|||
channel_name=channel_obj.name,
|
||||
client_ip=self.client_ip,
|
||||
client_id=self.client_id,
|
||||
user_agent=self.client_user_agent[:100] if self.client_user_agent else None
|
||||
user_agent=self.client_user_agent[:100] if self.client_user_agent else None,
|
||||
username=self.user.username if self.user else None
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not log client connect event: {e}")
|
||||
|
|
@ -593,7 +596,8 @@ class StreamGenerator:
|
|||
client_id=self.client_id,
|
||||
user_agent=self.client_user_agent[:100] if self.client_user_agent else None,
|
||||
duration=round(elapsed, 2),
|
||||
bytes_sent=self.bytes_sent
|
||||
bytes_sent=self.bytes_sent,
|
||||
username=self.user.username if self.user else None
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not log client disconnect event: {e}")
|
||||
|
|
@ -628,10 +632,10 @@ class StreamGenerator:
|
|||
|
||||
gevent.spawn(delayed_shutdown)
|
||||
|
||||
def create_stream_generator(channel_id, client_id, client_ip, client_user_agent, channel_initializing=False):
|
||||
def create_stream_generator(channel_id, client_id, client_ip, client_user_agent, channel_initializing=False, user=None):
|
||||
"""
|
||||
Factory function to create a new stream generator.
|
||||
Returns a function that can be passed to StreamingHttpResponse.
|
||||
"""
|
||||
generator = StreamGenerator(channel_id, client_id, client_ip, client_user_agent, channel_initializing)
|
||||
generator = StreamGenerator(channel_id, client_id, client_ip, client_user_agent, channel_initializing, user=user)
|
||||
return generator.generate
|
||||
|
|
|
|||
|
|
@ -532,7 +532,7 @@ def stream_ts(request, channel_id, user=None):
|
|||
|
||||
# Create a stream generator for this client
|
||||
generate = create_stream_generator(
|
||||
channel_id, client_id, client_ip, client_user_agent, channel_initializing
|
||||
channel_id, client_id, client_ip, client_user_agent, channel_initializing, user=user
|
||||
)
|
||||
|
||||
# Return the StreamingHttpResponse from the main function
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue