From 60c2e5a161da4db33f0555be8a7c6c7d818f8422 Mon Sep 17 00:00:00 2001 From: euzu Date: Fri, 24 Oct 2025 18:36:13 +0200 Subject: [PATCH] User Stream Connection table added --- CHANGELOG.md | 1 + README.md | 23 -- backend/src/api/api_utils.rs | 23 +- .../src/api/endpoints/api_playlist_utils.rs | 4 +- backend/src/api/endpoints/hdhomerun_api.rs | 4 +- backend/src/api/endpoints/hls_api.rs | 65 +++++- backend/src/api/endpoints/m3u_api.rs | 11 +- backend/src/api/endpoints/user_api.rs | 4 +- backend/src/api/endpoints/v1_api.rs | 15 +- backend/src/api/endpoints/websocket_api.rs | 4 +- backend/src/api/endpoints/xtream_api.rs | 18 +- backend/src/api/model/active_user_manager.rs | 51 +++-- backend/src/api/model/event_manager.rs | 22 +- .../api/model/streams/active_client_stream.rs | 13 +- backend/src/auth/fingerprint.rs | 8 +- backend/src/model/config/target.rs | 10 +- backend/src/processing/parser/xmltv.rs | 2 +- backend/src/processing/processor/playlist.rs | 68 +++--- backend/src/processing/processor/sort.rs | 6 +- frontend/public/assets/fonts.css | 208 +++++++++--------- .../assets/fonts/montserrat-regular.woff | Bin 0 -> 111676 bytes frontend/public/assets/i18n/en.json | 8 +- frontend/public/assets/icons.json | 8 + frontend/scss/app/_component.scss | 1 + frontend/scss/app/components/_no_content.scss | 1 + .../components/dashboard/_streams_view.scss | 21 ++ frontend/scss/main.scss | 2 +- frontend/src/app/components/dashboard/mod.rs | 5 + .../app/components/dashboard/streams_table.rs | 191 ++++++++++++++++ .../app/components/dashboard/streams_view.rs | 22 ++ frontend/src/app/components/home.rs | 6 +- .../app/components/playlist/target_table.rs | 22 +- frontend/src/app/components/sidebar.rs | 2 + frontend/src/hooks/use_server_status.rs | 20 +- frontend/src/hooks/use_service_context.rs | 6 +- frontend/src/model/event_message.rs | 4 +- frontend/src/model/view_type.rs | 4 + frontend/src/services/mod.rs | 4 +- frontend/src/services/streams_service.rs | 27 +++ frontend/src/services/websocket_service.rs | 4 +- .../model/active_user_connection_change.rs | 8 + shared/src/model/mod.rs | 4 + shared/src/model/playlist.rs | 16 +- shared/src/model/status_check.rs | 3 + shared/src/model/stream_info.rs | 59 +++++ shared/src/model/web_socket.rs | 4 +- 46 files changed, 728 insertions(+), 284 deletions(-) create mode 100644 frontend/public/assets/fonts/montserrat-regular.woff create mode 100644 frontend/scss/app/components/dashboard/_streams_view.scss create mode 100644 frontend/src/app/components/dashboard/streams_table.rs create mode 100644 frontend/src/app/components/dashboard/streams_view.rs create mode 100644 frontend/src/services/streams_service.rs create mode 100644 shared/src/model/active_user_connection_change.rs create mode 100644 shared/src/model/stream_info.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d9d9616..31e328dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Output filters are applied after all transformations have been performed, theref - Added burst buffer to shared stream - Telegram message thread support. thread id can now be appended to chat-id like `chat-id:thread-id`. - Telegram supports markdown generation for structured json messages. simply set `markdown: true` in telegram config. +- Added User-Stream-Connections Table to WebUI # 3.1.7 (2025-10-10) - Added Dark/Bright theme switch diff --git a/README.md b/README.md index 4728abce6..b011b76d0 100644 --- a/README.md +++ b/README.md @@ -775,7 +775,6 @@ Has the following top level entries: - `filter` _mandatory_, - `rename` _optional_ - `mapping` _optional_ -- `favourites` _optional_ - `watch` _optional_ - `use_memory_cache`, default is false. If set to `true` playlist is cached into memory to reduce disc access. Placing playlist into memory causes more RAM usage but reduces disk access. @@ -1113,28 +1112,6 @@ messaging: - watch ``` -### 2.5.2.9 `favourites` -The Favourites feature allows users to define `custom playlist groups` that automatically collect channels based on filter rules. -Each favourite definition specifies: - -A `group name` (the name under which the favourite channels will appear in the playlist). - -A `filter expression` (which channels should be included in that group). - -During playlist processing, the system scans all existing playlist groups and channels. -For each configured favourite, it creates a new virtual group that contains all channels matching the filter condition. - -This allows a channel to appear in multiple groups — both its original one and any favourite groups where it matches. - -Example -```yaml -favourites: - - group: "[Sports] Tennis" - filter: 'Group ~ "EU: Sports" AND Caption ~ "Davis Cup Qualifiers"' - - group: "[Sports] Soccer" - filter: 'Group ~ "EU: Sports" AND Caption ~ "FIFA World Cup"' -``` - ## 2. `mapping.yml` Has the root item `mappings` which has the following top level entries: - `templates` _optional_ diff --git a/backend/src/api/api_utils.rs b/backend/src/api/api_utils.rs index 7bfc806e0..4d7da1ef3 100644 --- a/backend/src/api/api_utils.rs +++ b/backend/src/api/api_utils.rs @@ -25,10 +25,7 @@ use futures::{StreamExt, TryStreamExt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use log::{debug, error, log_enabled, trace}; use reqwest::header::RETRY_AFTER; -use shared::model::{ - Claims, InputFetchMethod, PlaylistEntry, PlaylistItemType, TargetType, - UserConnectionPermission, XtreamCluster, -}; +use shared::model::{Claims, InputFetchMethod, PlaylistEntry, PlaylistItemType, StreamChannel, TargetType, UserConnectionPermission, XtreamCluster}; use shared::utils::{bin_serialize, default_grace_period_millis, human_readable_byte_size, trim_slash}; use shared::utils::{ extract_extension_from_url, replace_url_extension, sanitize_sensitive_info, DASH_EXT, HLS_EXT, @@ -738,7 +735,7 @@ pub async fn force_provider_stream_response( addr: &str, app_state: &AppState, user_session: &UserSession, - item_type: PlaylistItemType, + stream_channel: StreamChannel, req_headers: &HeaderMap, input: &ConfigInput, user: &ProxyUserCredentials, @@ -746,6 +743,7 @@ pub async fn force_provider_stream_response( let stream_options = get_stream_options(app_state); let share_stream = false; let connection_permission = UserConnectionPermission::Allowed; + let item_type = stream_channel.item_type; let mut stream_details = create_stream_response_details( app_state, @@ -771,7 +769,7 @@ pub async fn force_provider_stream_response( .update_session_addr(&user.username, &user_session.token, addr) .await; let stream = - ActiveClientStream::new(stream_details, app_state, user, connection_permission, addr) + ActiveClientStream::new(stream_details, app_state, user, connection_permission, addr, stream_channel) .await; let (status_code, header_map) = @@ -809,8 +807,7 @@ pub async fn stream_response( addr: &str, app_state: &AppState, session_token: &str, - virtual_id: u32, - item_type: PlaylistItemType, + stream_channel: StreamChannel, stream_url: &str, req_headers: &HeaderMap, input: &ConfigInput, @@ -830,10 +827,13 @@ pub async fn stream_response( .into_response(); } + let virtual_id = stream_channel.virtual_id; + let item_type = stream_channel.item_type; + let share_stream = is_stream_share_enabled(item_type, target); if share_stream { if let Some(value) = - shared_stream_response(app_state, stream_url, addr, user, connection_permission).await + shared_stream_response(app_state, stream_url, addr, user, connection_permission, stream_channel.clone()).await { return value.into_response(); } @@ -870,7 +870,7 @@ pub async fn stream_response( None }; let stream = - ActiveClientStream::new(stream_details, app_state, user, connection_permission, addr) + ActiveClientStream::new(stream_details, app_state, user, connection_permission, addr, stream_channel) .await; let stream_resp = if share_stream { debug_if_enabled!( @@ -984,6 +984,7 @@ async fn shared_stream_response( addr: &str, user: &ProxyUserCredentials, connect_permission: UserConnectionPermission, + stream_channel: StreamChannel ) -> Option { if let Some(stream) = SharedStreamManager::subscribe_shared_stream(app_state, stream_url, Some(addr)).await @@ -1003,7 +1004,7 @@ async fn shared_stream_response( ))); let stream_details = StreamDetails::from_stream(stream); let stream = - ActiveClientStream::new(stream_details, app_state, user, connect_permission, addr) + ActiveClientStream::new(stream_details, app_state, user, connect_permission, addr, stream_channel) .await .boxed(); let mut response = axum::response::Response::builder().status(status_code); diff --git a/backend/src/api/endpoints/api_playlist_utils.rs b/backend/src/api/endpoints/api_playlist_utils.rs index d8af876c8..bb84329da 100644 --- a/backend/src/api/endpoints/api_playlist_utils.rs +++ b/backend/src/api/endpoints/api_playlist_utils.rs @@ -128,7 +128,7 @@ async fn grouped_channels( pub(in crate::api::endpoints) async fn get_playlist_for_target(cfg_target: Option<&ConfigTarget>, cfg: &AppConfig, accept: Option<&String>) -> impl axum::response::IntoResponse + Send { if let Some(target) = cfg_target { - if target.has_output(&TargetType::Xtream) { + if target.has_output(TargetType::Xtream) { let live_channels = grouped_channels(cfg, target, XtreamCluster::Live).await; let vod_channels = grouped_channels(cfg, target, XtreamCluster::Video).await; let series_channels = grouped_channels(cfg, target, XtreamCluster::Series).await; @@ -140,7 +140,7 @@ pub(in crate::api::endpoints) async fn get_playlist_for_target(cfg_target: Optio }; return json_or_bin_response(accept, &response).into_response(); - } else if target.has_output(&TargetType::M3u) { + } else if target.has_output(TargetType::M3u) { let all_channels = m3u_repository::iter_raw_m3u_playlist(cfg, target).await; let (live_channels, vod_channels, series_channels) = group_playlist_items_by_cluster(all_channels); let response = PlaylistCategoriesResponse { diff --git a/backend/src/api/endpoints/hdhomerun_api.rs b/backend/src/api/endpoints/hdhomerun_api.rs index c7787d99a..0136ad79a 100644 --- a/backend/src/api/endpoints/hdhomerun_api.rs +++ b/backend/src/api/endpoints/hdhomerun_api.rs @@ -224,7 +224,7 @@ async fn lineup(app_state: &Arc, cfg: &Arc, creden let use_all = use_output.is_none(); let use_m3u = use_output.as_ref() == Some(&TargetType::M3u); let use_xtream = use_output.as_ref() == Some(&TargetType::Xtream); - if (use_all || use_m3u) && target.has_output(&TargetType::M3u) { + if (use_all || use_m3u) && target.has_output(TargetType::M3u) { let iterator = M3uPlaylistIterator::new(cfg, target, credentials).await.ok(); let stream = m3u_item_to_lineup_stream(iterator); let body_stream = stream::once(async { Ok(Bytes::from("[")) }) @@ -234,7 +234,7 @@ async fn lineup(app_state: &Arc, cfg: &Arc, creden .status(axum::http::StatusCode::OK) .header(axum::http::header::CONTENT_TYPE, mime::APPLICATION_JSON.to_string()) .body(axum::body::Body::from_stream(body_stream))); - } else if (use_all || use_xtream) && target.has_output(&TargetType::Xtream) { + } else if (use_all || use_xtream) && target.has_output(TargetType::Xtream) { let server_info = app_state.app_state.app_config.get_user_server_info(credentials); let base_url = server_info.get_base_url(); diff --git a/backend/src/api/endpoints/hls_api.rs b/backend/src/api/endpoints/hls_api.rs index 85f57caa5..6af9cda86 100644 --- a/backend/src/api/endpoints/hls_api.rs +++ b/backend/src/api/endpoints/hls_api.rs @@ -7,7 +7,7 @@ use crate::api::model::UserSession; use crate::api::model::{create_custom_video_stream_response, CustomVideoStreamType}; use crate::api::model::AppState; use crate::auth::Fingerprint; -use crate::model::ProxyUserCredentials; +use crate::model::{ConfigTarget, ProxyUserCredentials}; use crate::model::{ConfigInput, InputSource}; use crate::processing::parser::hls::{ get_hls_session_token_and_url_from_token, rewrite_hls, RewriteHlsProps, @@ -17,9 +17,11 @@ use axum::http::HeaderMap; use axum::response::IntoResponse; use log::{debug, error}; use serde::Deserialize; -use shared::model::{PlaylistItemType, UserConnectionPermission, XtreamCluster}; +use shared::model::{PlaylistItemType, StreamChannel, TargetType, UserConnectionPermission, XtreamCluster}; use shared::utils::{is_hls_url, replace_url_extension, sanitize_sensitive_info, CUSTOM_VIDEO_PREFIX, HLS_EXT}; use std::sync::Arc; +use crate::repository::m3u_repository::m3u_get_item_for_stream_id; +use crate::repository::xtream_repository; #[derive(Debug, Deserialize)] struct HlsApiPathParams { @@ -151,6 +153,49 @@ pub(in crate::api) async fn handle_hls_stream_request( } } +async fn get_stream_channel(app_state: &Arc, target: &Arc, virtual_id: u32) -> Option { + if target.has_output(TargetType::Xtream) { + if let Ok((pli, _mapping)) = xtream_repository::xtream_get_item_for_stream_id( + virtual_id, + app_state, + target, + None + ).await { + Some(pli.to_stream_channel()) + } else { + None + } + } else { + match m3u_get_item_for_stream_id(virtual_id, app_state, target).await { + Ok(pli) => Some(pli.to_stream_channel()), + Err(_) => { None } + } + } +} + +async fn resolve_stream_channel( + app_state: &Arc, + target: &Arc, + virtual_id: u32, + hls_url: &str, +) -> StreamChannel { + let mut channel = match get_stream_channel(app_state, target, virtual_id).await { + Some(channel) => channel, + None => StreamChannel { + virtual_id, + provider_id: 0, + item_type: PlaylistItemType::LiveHls, + cluster: XtreamCluster::Live, + group: "Unknown".to_string(), + title: "Unknown".to_string(), + url: hls_url.to_string(), + }, + }; + + channel.item_type = PlaylistItemType::LiveHls; + channel +} + #[allow(clippy::too_many_lines)] async fn hls_api_stream( Fingerprint(fingerprint, addr): Fingerprint, @@ -207,7 +252,7 @@ async fn hls_api_stream( &app_state.app_config, CustomVideoStreamType::ProviderConnectionsExhausted, ) - .into_response(); + .into_response(); } let hls_url = match get_hls_session_token_and_url_from_token( @@ -218,21 +263,22 @@ async fn hls_api_stream( _ => return axum::http::StatusCode::BAD_REQUEST.into_response(), }; - session.stream_url = hls_url; + session.stream_url.clone_from(&hls_url); if session.virtual_id == virtual_id { if is_seek_request(XtreamCluster::Live, &req_headers).await { // partial request means we are in reverse proxy mode, seek happened + let stream_channel = resolve_stream_channel(&app_state, &target, virtual_id, &hls_url).await; return force_provider_stream_response( &addr, &app_state, session, - PlaylistItemType::LiveHls, + stream_channel, &req_headers, &input, &user, ) - .await - .into_response(); + .await + .into_response(); } } else { return axum::http::StatusCode::BAD_REQUEST.into_response(); @@ -264,11 +310,13 @@ async fn hls_api_stream( .into_response(); } + let stream_channel = resolve_stream_channel(&app_state, &target, virtual_id, &hls_url).await; + force_provider_stream_response( &addr, &app_state, session, - PlaylistItemType::LiveHls, + stream_channel, &req_headers, &input, &user, @@ -280,6 +328,7 @@ async fn hls_api_stream( } } + pub fn hls_api_register() -> axum::Router> { axum::Router::new().route( "/hls/{username}/{password}/{input_id}/{stream_id}/{token}", diff --git a/backend/src/api/endpoints/m3u_api.rs b/backend/src/api/endpoints/m3u_api.rs index 27621c81b..f59836d28 100644 --- a/backend/src/api/endpoints/m3u_api.rs +++ b/backend/src/api/endpoints/m3u_api.rs @@ -102,7 +102,7 @@ async fn m3u_api_stream( } let target_name = &target.name; - if !target.has_output(&TargetType::M3u) { + if !target.has_output(TargetType::M3u) { debug!("Target has no m3u playlist {target_name}"); return axum::http::StatusCode::BAD_REQUEST.into_response(); } @@ -154,7 +154,7 @@ async fn m3u_api_stream( addr, app_state, session, - pli.item_type, + pli.to_stream_channel(), req_headers, &input, &user, @@ -188,7 +188,7 @@ async fn m3u_api_stream( user: &user, stream_ext: stream_ext.as_deref(), req_context: context, - action_path: "", // TODO is there timeshoft or something like that ? + action_path: "", // TODO is there timeshift or something like that ? }; if let Some(response) = redirect_response(app_state, &redirect_params).await { @@ -225,8 +225,7 @@ async fn m3u_api_stream( addr, app_state, &session_key, - pli.virtual_id, - pli.item_type, + pli.to_stream_channel(), session_url, req_headers, &input, @@ -262,7 +261,7 @@ async fn m3u_api_resource( } let target_name = &target.name; - if !target.has_output(&TargetType::M3u) { + if !target.has_output(TargetType::M3u) { debug!("Target has no m3u playlist {target_name}"); return axum::http::StatusCode::BAD_REQUEST.into_response(); } diff --git a/backend/src/api/endpoints/user_api.rs b/backend/src/api/endpoints/user_api.rs index f4b11195b..03a8646bc 100644 --- a/backend/src/api/endpoints/user_api.rs +++ b/backend/src/api/endpoints/user_api.rs @@ -51,7 +51,7 @@ async fn playlist_categories( return axum::http::StatusCode::FORBIDDEN.into_response(); } let target_name = &target.name; - let xtream_stream = if target.has_output(&TargetType::Xtream) { + let xtream_stream = if target.has_output(TargetType::Xtream) { let config = &app_state.app_config.config.load(); let live_categories = get_categories_from_xtream(xtream_get_playlist_categories(config, target_name, XtreamCluster::Live).await); let vod_categories = get_categories_from_xtream(xtream_get_playlist_categories(config, target_name, XtreamCluster::Video).await); @@ -69,7 +69,7 @@ async fn playlist_categories( stream::iter(vec![Ok::(Bytes::from(r#"{"live":[],"vod":[],"series":[]}"#))]) }; - let m3u_stream = if target.has_output(&TargetType::M3u) { + let m3u_stream = if target.has_output(TargetType::M3u) { let live_categories = get_categories_from_m3u_playlist(&target, &app_state.app_config).await; stream::iter(vec![ Ok::(Bytes::from(r#"{"live": "#)), diff --git a/backend/src/api/endpoints/v1_api.rs b/backend/src/api/endpoints/v1_api.rs index 4339cf709..8eddc7125 100644 --- a/backend/src/api/endpoints/v1_api.rs +++ b/backend/src/api/endpoints/v1_api.rs @@ -1,4 +1,4 @@ -use crate::api::api_utils::try_unwrap_body; +use crate::api::api_utils::{json_or_bin_response, try_unwrap_body}; use crate::api::endpoints::download_api; use crate::api::endpoints::user_api::user_api_register; use crate::api::endpoints::v1_api_playlist::v1_api_playlist_register; @@ -12,6 +12,7 @@ use shared::model::{IpCheckDto,StatusCheck}; use shared::utils::{concat_path_leading_slash}; use std::collections::BTreeMap; use std::sync::Arc; +use crate::api::endpoints::extract_accept_header::ExtractAcceptHeader; use crate::api::endpoints::v1_api_config::v1_api_config_register; async fn create_ipinfo_check(app_state: &Arc) -> Option<(Option, Option)> { @@ -31,9 +32,9 @@ pub async fn create_status_check(app_state: &Arc) -> StatusCheck { Some(lock.lock().await.get_size_text()) } }; - let (active_users, active_user_connections) = { + let (active_users, active_user_connections, active_user_streams) = { let active_user = &app_state.active_users; - (active_user.active_users().await, active_user.active_connections().await) + (active_user.active_users().await, active_user.active_connections().await, active_user.active_streams().await) }; let active_provider_connections = app_state.active_provider.active_connections().await.map(|c| c.into_iter().collect::>()); @@ -47,6 +48,7 @@ pub async fn create_status_check(app_state: &Arc) -> StatusCheck { active_users, active_user_connections, active_provider_connections, + active_user_streams, cache, } } @@ -59,6 +61,12 @@ async fn status(axum::extract::State(app_state): axum::extract::State>) -> axum::response::Response { + let streams = app_state.active_users.active_streams().await; + json_or_bin_response(accept.as_ref(), &streams).into_response() +} + async fn ipinfo(axum::extract::State(app_state): axum::extract::State>) -> axum::response::Response { if let Some((ipv4, ipv6)) = create_ipinfo_check(&app_state).await { let ipcheck = IpCheckDto { @@ -78,6 +86,7 @@ pub fn v1_api_register(web_auth_enabled: bool, app_state: Arc, web_ui_ let mut router = axum::Router::new(); router = router .route("/status", axum::routing::get(status)) + .route("/streams", axum::routing::get(streams)) .route("/file/download", axum::routing::post(download_api::queue_download_file)) .route("/file/download/info", axum::routing::get(download_api::download_file_info)) .route("/ipinfo", axum::routing::get(ipinfo)); diff --git a/backend/src/api/endpoints/websocket_api.rs b/backend/src/api/endpoints/websocket_api.rs index 0c64220ca..ced702c15 100644 --- a/backend/src/api/endpoints/websocket_api.rs +++ b/backend/src/api/endpoints/websocket_api.rs @@ -201,8 +201,8 @@ async fn handle_event_message(socket: &mut WebSocket, event: EventMessage, handl .await .map_err(|e| format!("Server Error event: {e} "))?; } - EventMessage::ActiveUser(users, connections) => { - let msg = ProtocolMessage::ActiveUserResponse(users, connections) + EventMessage::ActiveUser(event) => { + let msg = ProtocolMessage::ActiveUserResponse(event) .to_bytes() .map_err(|e| e.to_string())?; socket diff --git a/backend/src/api/endpoints/xtream_api.rs b/backend/src/api/endpoints/xtream_api.rs index 0ed14ef16..e67ef1d9f 100644 --- a/backend/src/api/endpoints/xtream_api.rs +++ b/backend/src/api/endpoints/xtream_api.rs @@ -240,7 +240,7 @@ async fn xtream_player_api_stream( } let target_name = &target.name; - if !target.has_output(&TargetType::Xtream) { + if !target.has_output(TargetType::Xtream) { debug!("Target has no xtream codes playlist {target_name}"); return axum::http::StatusCode::BAD_REQUEST.into_response(); } @@ -306,7 +306,7 @@ async fn xtream_player_api_stream( addr, app_state, session, - item_type, + pli.to_stream_channel(), req_headers, &input, &user, @@ -392,8 +392,7 @@ async fn xtream_player_api_stream( addr, app_state, session_key.as_str(), - pli.virtual_id, - item_type, + pli.to_stream_channel(), &stream_url, req_headers, &input, @@ -417,7 +416,7 @@ async fn xtream_player_api_stream_with_token( ) -> impl IntoResponse + Send { if let Some(target) = app_state.app_config.get_target_by_id(target_id) { let target_name = &target.name; - if !target.has_output(&TargetType::Xtream) { + if !target.has_output(TargetType::Xtream) { debug!("Target has no xtream output {target_name}"); return axum::http::StatusCode::BAD_REQUEST.into_response(); } @@ -524,8 +523,7 @@ async fn xtream_player_api_stream_with_token( addr, app_state, session_key.as_str(), - pli.virtual_id, - pli.item_type, + pli.to_stream_channel(), &stream_url, req_headers, &input, @@ -720,7 +718,7 @@ async fn xtream_player_api_resource( return axum::http::StatusCode::FORBIDDEN.into_response(); } let target_name = &target.name; - if !target.has_output(&TargetType::Xtream) { + if !target.has_output(TargetType::Xtream) { debug!("Target has no xtream output {target_name}"); return axum::http::StatusCode::BAD_REQUEST.into_response(); } @@ -1046,7 +1044,7 @@ async fn xtream_get_short_epg( limit: &str, ) -> impl IntoResponse + Send { let target_name = &target.name; - if target.has_output(&TargetType::Xtream) { + if target.has_output(TargetType::Xtream) { let virtual_id: u32 = match FromStr::from_str(stream_id.trim()) { Ok(id) => id, Err(_) => return axum::http::StatusCode::BAD_REQUEST.into_response(), @@ -1337,7 +1335,7 @@ async fn xtream_player_api( ) -> impl IntoResponse + Send { let user_target = get_user_target(&api_req, app_state); if let Some((user, target)) = user_target { - if !target.has_output(&TargetType::Xtream) { + if !target.has_output(TargetType::Xtream) { return axum::response::Json(get_user_info(&user, app_state).await).into_response(); } diff --git a/backend/src/api/model/active_user_manager.rs b/backend/src/api/model/active_user_manager.rs index 2ed812b10..45d26d478 100644 --- a/backend/src/api/model/active_user_manager.rs +++ b/backend/src/api/model/active_user_manager.rs @@ -4,7 +4,7 @@ use crate::model::Config; use crate::model::ProxyUserCredentials; use jsonwebtoken::get_current_timestamp; use log::{debug, error, info}; -use shared::model::UserConnectionPermission; +use shared::model::{ActiveUserConnectionChange, StreamChannel, StreamInfo, UserConnectionPermission}; use shared::utils::{current_time_secs, default_grace_period_millis, default_grace_period_timeout_secs, sanitize_sensitive_info}; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; @@ -16,8 +16,8 @@ const USER_GC_TTL: u64 = 900; // 15 Min const USER_CON_TTL: u64 = 10_800; // 3 hours const USER_SESSION_LIMIT: usize = 50; -type ActiveUserConnectionChangeSender = tokio::sync::mpsc::Sender<(usize, usize)>; -pub type ActiveUserConnectionChangeReceiver = tokio::sync::mpsc::Receiver<(usize, usize)>; +type ActiveUserConnectionChangeSender = tokio::sync::mpsc::Sender; +pub type ActiveUserConnectionChangeReceiver = tokio::sync::mpsc::Receiver; macro_rules! active_user_manager_shared_impl { () => { @@ -35,11 +35,10 @@ macro_rules! active_user_manager_shared_impl { async fn log_active_user(&self) { let user = Arc::clone(&self.user); - let connection_change_tx = self.connection_change_tx.clone(); let is_log_user_enabled = self.is_log_user_enabled(); let user_connection_count = Self::get_active_connections(&user).await; let user_count = user.read().await.iter().filter(|(_, c)| c.connections > 0).count(); - let _= connection_change_tx.try_send((user_count, user_connection_count)); + let _= self.connection_change_tx.try_send(ActiveUserConnectionChange::Connections(user_count, user_connection_count)); if is_log_user_enabled { info!("Active Users: {user_count}, Active User Connections: {user_connection_count}"); } @@ -61,11 +60,13 @@ macro_rules! active_user_manager_shared_impl { connection_data.granted_grace = false; connection_data.grace_ts = 0; } + connection_data.streams.retain(|c| c.addr != addr); } } self.drop_connection(&addr); self.shared_stream_manager.release_connection(addr, true).await; self.provider_manager.release_connection(addr).await; + let _= self.connection_change_tx.try_send(ActiveUserConnectionChange::Disconnected(addr.to_string())); self.log_active_user().await; } }; @@ -132,6 +133,7 @@ struct UserConnectionData { granted_grace: bool, grace_ts: u64, sessions: Vec, + streams: Vec, ts: u64, } @@ -143,6 +145,7 @@ impl UserConnectionData { granted_grace: false, grace_ts: 0, sessions: Vec::new(), + streams: Vec::new(), ts: current_time_secs(), } } @@ -277,16 +280,24 @@ impl ActiveUserManager { Self::get_active_connections(&self.user).await } - pub async fn add_connection(&self, username: &str, max_connections: u32, addr: &str) -> UserConnectionGuard { + pub async fn add_connection(&self, username: &str, max_connections: u32, addr: &str, provider: &str, stream_channel: StreamChannel) -> UserConnectionGuard { + let stream_info = StreamInfo::new( + username, + addr, + provider, + stream_channel, + ); { let mut user_map = self.user.write().await; - user_map - .entry(username.to_string()) - .and_modify(|connection_data| { - connection_data.connections += 1; - connection_data.max_connections = max_connections; - }) - .or_insert_with(|| UserConnectionData::new(1, max_connections)); + if let Some(connection_data) = user_map.get_mut(username) { + connection_data.connections += 1; + connection_data.max_connections = max_connections; + connection_data.streams.push(stream_info.clone()); + } else { + let mut connection_data = UserConnectionData::new(1, max_connections); + connection_data.streams.push(stream_info.clone()); + user_map.insert(username.to_string(), connection_data); + } } { @@ -294,6 +305,7 @@ impl ActiveUserManager { user_by_addr.insert(addr.to_owned(), username.to_owned()); } + let _= self.connection_change_tx.try_send(ActiveUserConnectionChange::Connected(stream_info)); self.log_active_user().await; UserConnectionGuard { @@ -351,7 +363,7 @@ impl ActiveUserManager { } } - // If not session exists, create one + // If no session exists, create one debug!("Creating session for user {} with token {session_token} {}", user.username, sanitize_sensitive_info(stream_url)); let session = Self::new_user_session(session_token, virtual_id, provider, stream_url, addr, connection_permission); let token = session.token.clone(); @@ -408,6 +420,17 @@ impl ActiveUserManager { None } + pub async fn active_streams(&self) -> Vec { + let user_map = self.user.read().await; + let mut streams = Vec::new(); + for (_username, connection_data) in user_map.iter() { + for stream in &connection_data.streams { + streams.push(stream.clone()); + } + } + streams + } + fn gc(&self) { if let Some(gc_ts) = &self.gc_ts { let ts = gc_ts.load(Ordering::Acquire); diff --git a/backend/src/api/model/event_manager.rs b/backend/src/api/model/event_manager.rs index 7efaa7565..46a738d07 100644 --- a/backend/src/api/model/event_manager.rs +++ b/backend/src/api/model/event_manager.rs @@ -1,13 +1,13 @@ use log::{info, trace}; use tokio::task; -use shared::model::{ConfigType, PlaylistUpdateState}; +use shared::model::{ActiveUserConnectionChange, ConfigType, PlaylistUpdateState}; use crate::api::model::{ActiveUserConnectionChangeReceiver}; use crate::api::model::{ProviderConnectionChangeReceiver}; #[derive(Clone, PartialEq)] pub enum EventMessage { ServerError(String), - ActiveUser(usize, usize), // user_count, connection count + ActiveUser(ActiveUserConnectionChange), // user_count, connection count ActiveProvider(String, usize), // provider name, connections ConfigChange(ConfigType), PlaylistUpdate(PlaylistUpdateState), @@ -30,22 +30,22 @@ impl EventManager { task::spawn(async move { loop { tokio::select! { - Some((user_count, connection_count)) = active_user_change_rx.recv() => { - if let Err(e) = channel_tx_clone.send(EventMessage::ActiveUser(user_count, connection_count)) { - trace!("Failed to send active user change event: {e}"); - } - } + Some(event) = active_user_change_rx.recv() => { + if let Err(e) = channel_tx_clone.send(EventMessage::ActiveUser(event)) { + trace!("Failed to send active user change event: {e}"); + } + } - Some((provider, connection_count)) = provider_change_rx.recv() => { + Some((provider, connection_count)) = provider_change_rx.recv() => { if let Err(e) = channel_tx_clone.send(EventMessage::ActiveProvider(provider, connection_count)) { trace!("Failed to send active provider change event: {e}"); } - } - else => { + } + else => { // Both channels are closed, exit gracefully info!("All input channels closed, terminating event manager task"); break; - } + } } } }); diff --git a/backend/src/api/model/streams/active_client_stream.rs b/backend/src/api/model/streams/active_client_stream.rs index a0d94810e..cb2613fd6 100644 --- a/backend/src/api/model/streams/active_client_stream.rs +++ b/backend/src/api/model/streams/active_client_stream.rs @@ -10,7 +10,7 @@ use bytes::Bytes; use futures::Stream; use futures::StreamExt; use log::{error, info}; -use shared::model::UserConnectionPermission; +use shared::model::{StreamChannel, UserConnectionPermission}; use std::pin::Pin; use std::sync::atomic::AtomicU8; use std::sync::{Arc}; @@ -38,13 +38,20 @@ impl ActiveClientStream { app_state: &AppState, user: &ProxyUserCredentials, connection_permission: UserConnectionPermission, - addr: &str) -> Self { + addr: &str, + stream_channel: StreamChannel) -> Self { if connection_permission == UserConnectionPermission::Exhausted { error!("Something is wrong this should not happen"); } let grant_user_grace_period = connection_permission == UserConnectionPermission::GracePeriod; let username = user.username.as_str(); - let user_connection_guard = Some(app_state.active_users.add_connection(username, user.max_connections, addr).await); + let provider_name = stream_details + .provider_connection_guard + .as_ref() + .and_then(|guard| guard.get_provider_name()) + .as_deref() + .map_or_else(String::new, ToString::to_string); + let user_connection_guard = Some(app_state.active_users.add_connection(username, user.max_connections, addr, &provider_name, stream_channel).await); let cfg = &app_state.app_config; let waker = Some(Arc::new(AtomicWaker::new())); let waker_clone = waker.clone(); diff --git a/backend/src/auth/fingerprint.rs b/backend/src/auth/fingerprint.rs index a401b9e53..791f09459 100644 --- a/backend/src/auth/fingerprint.rs +++ b/backend/src/auth/fingerprint.rs @@ -59,12 +59,18 @@ impl Fingerprint { } } + let client_ip = format!("{}:{}", real_ip.as_ref() + .map(ToString::to_string) + .or(forwarded_for.as_ref().map(ToString::to_string)) + .unwrap_or_else(|| addr.ip().to_string()), + addr.port()); + let ua = user_agent.unwrap_or_else(String::new); let key = match real_ip.or(forwarded_for) { Some(xff) => format!("{}{xff}{ua}", addr.ip()), None => format!("{}{ua}", addr.ip()), }; - Ok(Fingerprint(key, addr.to_string())) + Ok(Fingerprint(key, client_ip)) } } \ No newline at end of file diff --git a/backend/src/model/config/target.rs b/backend/src/model/config/target.rs index 3ce9f2ede..6401281ed 100644 --- a/backend/src/model/config/target.rs +++ b/backend/src/model/config/target.rs @@ -265,13 +265,13 @@ impl ConfigTarget { } } - pub fn has_output(&self, tt: &TargetType) -> bool { + pub fn has_output(&self, tt: TargetType) -> bool { for target_output in &self.output { match target_output { - TargetOutput::Xtream(_) => { if tt == &TargetType::Xtream { return true; } } - TargetOutput::M3u(_) => { if tt == &TargetType::M3u { return true; } } - TargetOutput::Strm(_) => { if tt == &TargetType::Strm { return true; } } - TargetOutput::HdHomeRun(_) => { if tt == &TargetType::HdHomeRun { return true; } } + TargetOutput::Xtream(_) => { if tt == TargetType::Xtream { return true; } } + TargetOutput::M3u(_) => { if tt == TargetType::M3u { return true; } } + TargetOutput::Strm(_) => { if tt == TargetType::Strm { return true; } } + TargetOutput::HdHomeRun(_) => { if tt == TargetType::HdHomeRun { return true; } } } } false diff --git a/backend/src/processing/parser/xmltv.rs b/backend/src/processing/parser/xmltv.rs index dc0a26be7..b94dcd537 100644 --- a/backend/src/processing/parser/xmltv.rs +++ b/backend/src/processing/parser/xmltv.rs @@ -393,7 +393,7 @@ fn handle_text_tag(stack: &mut [XmlTag], e: &BytesText) { let t_fixed: Cow = if t.ends_with('\\') { let mut owned = t.to_string(); owned.pop(); - owned.push_str("'"); + owned.push_str("' "); Cow::Owned(owned) } else { Cow::Borrowed(t) diff --git a/backend/src/processing/processor/playlist.rs b/backend/src/processing/processor/playlist.rs index bb45dbac5..8e36a87f7 100644 --- a/backend/src/processing/processor/playlist.rs +++ b/backend/src/processing/processor/playlist.rs @@ -61,41 +61,41 @@ pub fn apply_filter_to_playlist(playlist: &mut [PlaylistGroup], filter: &Filter) } pub fn apply_favourites_to_playlist( - playlist: &mut Vec, - favourites_cfg: Option<&Vec>, + _playlist: &mut Vec, + _favourites_cfg: Option<&Vec>, ) { - if let Some(favourites) = favourites_cfg { - let mut fav_groups: HashMap> = HashMap::new(); - - for pg in playlist.iter_mut() { - for pli in &pg.channels { - for fav in favourites { - if is_valid(pli, &fav.filter) { - let mut channel = pli.clone(); - channel.header.copy = true; - channel.header.group.clone_from(&fav.group); - channel.header.gen_uuid(); - fav_groups - .entry(fav.group.clone()) - .or_default() - .push(channel); - } - } - } - } - - for (group_name, channels) in fav_groups { - if !channels.is_empty() { - let xtream_cluster = channels[0].header.xtream_cluster; - playlist.push(PlaylistGroup { - id: 0, - title: group_name, - channels, - xtream_cluster, - }); - } - } - } + // if let Some(favourites) = favourites_cfg { + // let mut fav_groups: HashMap> = HashMap::new(); + // + // for pg in playlist.iter_mut() { + // for pli in &pg.channels { + // for fav in favourites { + // if is_valid(pli, &fav.filter) { + // let mut channel = pli.clone(); + // channel.header.copy = true; + // channel.header.group.clone_from(&fav.group); + // channel.header.gen_uuid(); + // fav_groups + // .entry(fav.group.clone()) + // .or_default() + // .push(channel); + // } + // } + // } + // } + // + // for (group_name, channels) in fav_groups { + // if !channels.is_empty() { + // let xtream_cluster = channels[0].header.xtream_cluster; + // playlist.push(PlaylistGroup { + // id: 0, + // title: group_name, + // channels, + // xtream_cluster, + // }); + // } + // } + // } } fn filter_playlist(playlist: &mut [PlaylistGroup], target: &ConfigTarget) -> Option> { diff --git a/backend/src/processing/processor/sort.rs b/backend/src/processing/processor/sort.rs index 38e34ab0b..3f2d2d26f 100644 --- a/backend/src/processing/processor/sort.rs +++ b/backend/src/processing/processor/sort.rs @@ -65,7 +65,11 @@ fn playlist_comparator( } } - Ordering::Equal + let o = value_a.cmp(value_b); + match order { + SortOrder::Asc => o, + SortOrder::Desc => o.reverse(), + } } (Some(_), None) => match order { SortOrder::Asc => Ordering::Less, diff --git a/frontend/public/assets/fonts.css b/frontend/public/assets/fonts.css index 2f6f658f8..4ce6e53dd 100644 --- a/frontend/public/assets/fonts.css +++ b/frontend/public/assets/fonts.css @@ -1,142 +1,142 @@ @font-face { - font-family: 'Montserrat Regular'; + font-family: 'Montserrat'; font-style: normal; font-weight: normal; - src: local('Montserrat Regular'), url('fonts/Montserrat-Regular.woff') format('woff'); + src: local('Montserrat'), url('fonts/montserrat-regular.woff') format('woff'); } -@font-face { - font-family: 'Montserrat Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Italic'), url('fonts/Montserrat-Italic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Italic'), url('fonts/montserrat-italic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Thin'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Thin'), url('fonts/Montserrat-Thin.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Thin';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Thin'), url('fonts/montserrat-thin.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Thin Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Thin Italic'), url('fonts/Montserrat-ThinItalic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Thin Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Thin Italic'), url('fonts/montserrat-thinitalic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat ExtraLight'; - font-style: normal; - font-weight: normal; - src: local('Montserrat ExtraLight'), url('fonts/Montserrat-ExtraLight.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat ExtraLight';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat ExtraLight'), url('fonts/Montserrat-ExtraLight.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat ExtraLight Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat ExtraLight Italic'), url('fonts/Montserrat-ExtraLightItalic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat ExtraLight Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat ExtraLight Italic'), url('fonts/Montserrat-ExtraLightItalic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Light'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Light'), url('fonts/Montserrat-Light.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Light';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Light'), url('fonts/Montserrat-Light.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Light Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Light Italic'), url('fonts/Montserrat-LightItalic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Light Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Light Italic'), url('fonts/Montserrat-LightItalic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Medium'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Medium'), url('fonts/Montserrat-Medium.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Medium';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Medium'), url('fonts/Montserrat-Medium.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Medium Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Medium Italic'), url('fonts/Montserrat-MediumItalic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Medium Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Medium Italic'), url('fonts/Montserrat-MediumItalic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat SemiBold'; - font-style: normal; - font-weight: normal; - src: local('Montserrat SemiBold'), url('fonts/Montserrat-SemiBold.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat SemiBold';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat SemiBold'), url('fonts/Montserrat-SemiBold.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat SemiBold Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat SemiBold Italic'), url('fonts/Montserrat-SemiBoldItalic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat SemiBold Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat SemiBold Italic'), url('fonts/Montserrat-SemiBoldItalic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Bold'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Bold'), url('fonts/Montserrat-Bold.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Bold';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Bold'), url('fonts/Montserrat-Bold.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Bold Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Bold Italic'), url('fonts/Montserrat-BoldItalic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Bold Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Bold Italic'), url('fonts/Montserrat-BoldItalic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat ExtraBold'; - font-style: normal; - font-weight: normal; - src: local('Montserrat ExtraBold'), url('fonts/Montserrat-ExtraBold.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat ExtraBold';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat ExtraBold'), url('fonts/Montserrat-ExtraBold.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat ExtraBold Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat ExtraBold Italic'), url('fonts/Montserrat-ExtraBoldItalic.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat ExtraBold Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat ExtraBold Italic'), url('fonts/Montserrat-ExtraBoldItalic.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Black'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Black'), url('fonts/Montserrat-Black.woff') format('woff'); -} +/*@font-face {*/ +/* font-family: 'Montserrat Black';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Black'), url('fonts/Montserrat-Black.woff') format('woff');*/ +/*}*/ -@font-face { - font-family: 'Montserrat Black Italic'; - font-style: normal; - font-weight: normal; - src: local('Montserrat Black Italic'), url('fonts/Montserrat-BlackItalic.woff') format('woff'); -} \ No newline at end of file +/*@font-face {*/ +/* font-family: 'Montserrat Black Italic';*/ +/* font-style: normal;*/ +/* font-weight: normal;*/ +/* src: local('Montserrat Black Italic'), url('fonts/Montserrat-BlackItalic.woff') format('woff');*/ +/*}*/ \ No newline at end of file diff --git a/frontend/public/assets/fonts/montserrat-regular.woff b/frontend/public/assets/fonts/montserrat-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..2a990e5d8475f05b075dd0e74a32009e0986e95e GIT binary patch literal 111676 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

f^b)zu68 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 WWS5Eq> zF2^mO0{+=cw*GR?k zni=H(;ooYF56d6g9A9IlwpQ(OnfPzLLrU3v=kuRcY90P!ar*Dvr!(ua!kjjnB=Z() zX_}svO`PWyoWY#?Y-{;U@61&(U3t$;qWmUbT^vxjiL9yiLRd@Hm`SzkNd(@uJi%B-eC7N4!Njh4zQuHrl7dGyW1Eq}~9^H+bD zO?fFO(xcWbIK|1w&)bgccm>DB&IRR3%@1Xs{5kJhapz9!Wmf^;o#NjYgz!GvV)1UV zsaoe9f8SpszfP6@e*g3P?3?cAQj{b;&+R-=er|v0SN-oES~}vsrmONuQ=JowP zxBKtvAnT10ce`sU@P%ix58R%dkX~`Q^hJ5tCAMpk8K;bubJFeC^Cop| z?!LaxWvWfLRVCYJ(c5PC9a5xjN1pqg{^8&3jHLLPR`NHiw2OT1e^NXh)9~|?%I%rV z)_HNSr|-YOsB_`yo5ii)r(In9=5tP!WN+ujdp$hA`~Gm;u4bJ1$?}!Oy{K2$6{}c( zwypZ~?5It>=GGf_SzoPvjMqQmxGlBeb7)58=Zt-Vs?R1S_Zgpmc~(^L`N=h%Glg#+ zsWyL@vQzrA#lFj>X7d+woBC;{oxE)kdM{+&BH9%U9Jymrj+L zRXsLKe&aG_WrW)k!+X1XuCvY5zog?Ll&pKObDHFHahv;x3`;idt&aQ`_={z^M3&X- z!nmB<=E@5D6W=ZN6L}f5Y-aQ&_L*sNzXNhro=ti`zjLQa@wUT<)4x9J?kG;ZcJxfQ zRfo>Wl)p}AQl-D9-LpI|S|z^h)J>V|k9S18D{tRX$GGjL-p&;Xzg3s{e_i)`=^v)K zoZ1oFBqYnu{>bs`xY=v8?fT1>6-}wDE~uJ2eaVVhbMJX=-|ch4#}8L(n!VqYW1my^ z+DG>Hx�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
    8MI{9A^{%TYexpz)n-1-IK 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, +} + +#[function_component] +pub fn StreamsTable(props: &StreamsTableProps) -> Html { + let translate = use_translation(); + let services = use_service_context(); + let popup_anchor_ref = use_state(|| None::); + let popup_is_open = use_state(|| false); + let selected_dto = use_state(|| None::>); + + let handle_popup_close = { + let set_is_open = popup_is_open.clone(); + Callback::from(move |()| { + set_is_open.set(false); + }) + }; + + let handle_popup_onclick = { + let set_selected_dto = selected_dto.clone(); + let set_anchor_ref = popup_anchor_ref.clone(); + let set_is_open = popup_is_open.clone(); + Callback::from(move |(dto, event): (Rc, MouseEvent)| { + if let Some(streams) = event.target_dyn_into::() { + set_selected_dto.set(Some(dto.clone())); + set_anchor_ref.set(Some(streams)); + set_is_open.set(true); + } + }) + }; + + let render_header_cell = { + let translator = translate.clone(); + Callback::::from(move |col| { + html! { + { + if col < HEADERS.len() { + translator.t(HEADERS[col]) + } else { + String::new() + } + } + } + }) + }; + + let render_data_cell = { + let popup_onclick = handle_popup_onclick.clone(); + Callback::<(usize, usize, Rc), Html>::from( + move |(row, col, dto): (usize, usize, Rc)| { + match col { + 0 => { + let popup_onclick = popup_onclick.clone(); + html! { + + } + } + 1 => html! {dto.username.as_str()}, + 2 => html! { <> + { dto.channel.virtual_id.to_string() } + {" ("} + { dto.channel.provider_id.to_string() } + {")"} + }, + 3 => html! {dto.channel.title.as_str()}, + 4 => html! {dto.channel.group.as_str()}, + 5 => html! {dto.addr.as_str()}, + 6 => html! {dto.provider.as_str()}, + _ => html! {""}, + } + }) + }; + + let is_sortable = Callback::::from(move |_col| { + false + }); + + let on_sort = Callback::, ()>::from(move |_args| { + }); + + let table_definition = { + // first register for config update + let render_header_cell_cb = render_header_cell.clone(); + let render_data_cell_cb = render_data_cell.clone(); + let is_sortable = is_sortable.clone(); + let on_sort = on_sort.clone(); + let num_cols = HEADERS.len(); + use_memo(props.streams.clone(), move |streams| { + streams.as_ref().map(|list| + Rc::new(TableDefinition:: { + items: if list.is_empty() {None} else {Some(Rc::new(list.clone()))}, + num_cols, + is_sortable, + on_sort, + render_header_cell: render_header_cell_cb, + render_data_cell: render_data_cell_cb, + })) + }) + }; + + + let handle_menu_click = { + let popup_is_open_state = popup_is_open.clone(); + //let translate = translate.clone(); + let services_ctx = services.clone(); + //let selected_dto = selected_dto.clone(); + Callback::from(move |(name, _): (String, _)| { + if let Ok(action) = StreamsTableAction::from_str(&name) { + match action { + StreamsTableAction::Kick => { + // TODO implement connection kick + services_ctx.toastr.error("Not implemented") + } + } + } + popup_is_open_state.set(false); + }) + }; + + html! { +
    + { + if let Some(definition) = table_definition.as_ref() { + html! { + <> + definition={definition.clone()} /> + + + + + } + } else { + html! {} + } + } +
    + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum StreamsTableAction { + Kick, +} + +impl Display for StreamsTableAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match self { + Self::Kick => "kick", + }) + } +} + +impl FromStr for StreamsTableAction { + type Err = TuliproxError; + + fn from_str(s: &str) -> Result { + if s.eq("kick") { + Ok(Self::Kick) + } else { + create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown Stream Action: {}", s) + } + } +} \ No newline at end of file diff --git a/frontend/src/app/components/dashboard/streams_view.rs b/frontend/src/app/components/dashboard/streams_view.rs new file mode 100644 index 000000000..49c9c6e92 --- /dev/null +++ b/frontend/src/app/components/dashboard/streams_view.rs @@ -0,0 +1,22 @@ +use std::rc::Rc; +use yew::prelude::*; +use yew_i18n::use_translation; +use crate::app::components::StreamsTable; +use crate::app::StatusContext; + +#[function_component] +pub fn StreamsView() -> Html { + let translate = use_translation(); + let status_ctx = use_context::().expect("Status context not found"); + + html! { +
    +
    +

    { translate.t("LABEL.STREAMS")}

    +
    +
    + +
    +
    + } +} \ No newline at end of file diff --git a/frontend/src/app/components/home.rs b/frontend/src/app/components/home.rs index acd16429e..45e6b19c2 100644 --- a/frontend/src/app/components/home.rs +++ b/frontend/src/app/components/home.rs @@ -1,4 +1,4 @@ -use crate::app::components::{AppIcon, DashboardView, EpgView, IconButton, InputRow, Panel, PlaylistEditorView, PlaylistExplorerView, PlaylistUpdateView, Sidebar, StatsView, ToastrView, UserlistView, WebsocketStatus}; +use crate::app::components::{AppIcon, DashboardView, EpgView, IconButton, InputRow, Panel, PlaylistEditorView, PlaylistExplorerView, PlaylistUpdateView, Sidebar, StatsView, StreamsView, ToastrView, UserlistView, WebsocketStatus}; use crate::app::context::{ConfigContext, PlaylistContext, StatusContext}; use crate::hooks::{use_server_status, use_service_context}; use crate::model::{EventMessage, ViewType}; @@ -135,6 +135,7 @@ pub fn Home() -> Html { sources: sources.clone(), }; + //
    html! { @@ -171,6 +172,9 @@ pub fn Home() -> Html { + + + diff --git a/frontend/src/app/components/playlist/target_table.rs b/frontend/src/app/components/playlist/target_table.rs index b4ac9398e..01abb0d04 100644 --- a/frontend/src/app/components/playlist/target_table.rs +++ b/frontend/src/app/components/playlist/target_table.rs @@ -151,10 +151,10 @@ pub fn TargetTable(props: &TargetTableProps) -> Html { let services_ctx = services.clone(); let selected_dto = selected_dto.clone(); Callback::from(move |(name, _): (String, _)| { - if let Ok(action) = TableAction::from_str(&name) { + if let Ok(action) = TargetTableAction::from_str(&name) { match action { - TableAction::Edit => {} - TableAction::Refresh => { + TargetTableAction::Edit => {} + TargetTableAction::Refresh => { let translate = translate.clone(); let services_ctx = services_ctx.clone(); let dto_name = selected_dto.as_ref().map_or_else(String::new, |d| d.name.to_string()); @@ -166,7 +166,7 @@ pub fn TargetTable(props: &TargetTableProps) -> Html { } }); } - TableAction::Delete => { + TargetTableAction::Delete => { let confirm = confirm.clone(); let translator = translate.clone(); spawn_local(async move { @@ -190,10 +190,10 @@ pub fn TargetTable(props: &TargetTableProps) -> Html { <> definition={definition.clone()} /> - - + +
    - +
    } @@ -207,13 +207,13 @@ pub fn TargetTable(props: &TargetTableProps) -> Html { #[derive(Debug, Clone, Eq, PartialEq)] -enum TableAction { +enum TargetTableAction { Edit, Refresh, Delete, } -impl Display for TableAction { +impl Display for TargetTableAction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", match self { Self::Edit => "edit", @@ -223,7 +223,7 @@ impl Display for TableAction { } } -impl FromStr for TableAction { +impl FromStr for TargetTableAction { type Err = TuliproxError; fn from_str(s: &str) -> Result { @@ -234,7 +234,7 @@ impl FromStr for TableAction { } else if s.eq("delete") { Ok(Self::Delete) } else { - create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown InputType: {}", s) + create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown Target Action: {}", s) } } } \ No newline at end of file diff --git a/frontend/src/app/components/sidebar.rs b/frontend/src/app/components/sidebar.rs index 1cdd70c20..7354f3f27 100644 --- a/frontend/src/app/components/sidebar.rs +++ b/frontend/src/app/components/sidebar.rs @@ -135,6 +135,7 @@ pub fn Sidebar(props: &SidebarProps) -> Html {
    + @@ -154,6 +155,7 @@ pub fn Sidebar(props: &SidebarProps) -> Html {
    + diff --git a/frontend/src/hooks/use_server_status.rs b/frontend/src/hooks/use_server_status.rs index 78695d72b..bf5568f6a 100644 --- a/frontend/src/hooks/use_server_status.rs +++ b/frontend/src/hooks/use_server_status.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use std::collections::BTreeMap; use gloo_timers::callback::Interval; use yew::prelude::*; -use shared::model::StatusCheck; +use shared::model::{ActiveUserConnectionChange, StatusCheck}; use crate::hooks::use_service_context; use yew::platform::spawn_local; use crate::model::EventMessage; @@ -27,7 +27,7 @@ pub fn use_server_status( *status_holder_signal.borrow_mut() = Some(Rc::clone(&server_status)); status_signal.set(Some(server_status)); } - EventMessage::ActiveUser(user_count, connections) => { + EventMessage::ActiveUser(event) => { let mut server_status = { if let Some(old_status) = status_holder_signal.borrow().as_ref() { (**old_status).clone() @@ -35,8 +35,20 @@ pub fn use_server_status( StatusCheck::default() } }; - server_status.active_users = user_count; - server_status.active_user_connections = connections; + + match event { + ActiveUserConnectionChange::Connected(stream_info) => { + server_status.active_user_streams.push(stream_info); + } + ActiveUserConnectionChange::Disconnected(addr) => { + server_status.active_user_streams.retain(|stream_info| stream_info.addr != addr); + } + ActiveUserConnectionChange::Connections(user_count, connections) => { + server_status.active_users = user_count; + server_status.active_user_connections = connections; + } + } + let new_status = Rc::new(server_status); *status_holder_signal.borrow_mut() = Some(Rc::clone(&new_status)); status_signal.set(Some(new_status)); diff --git a/frontend/src/hooks/use_service_context.rs b/frontend/src/hooks/use_service_context.rs index 0ea6e633b..d0463f154 100644 --- a/frontend/src/hooks/use_service_context.rs +++ b/frontend/src/hooks/use_service_context.rs @@ -1,14 +1,14 @@ use std::rc::Rc; use yew::prelude::*; use crate::model::WebConfig; -use crate::services::{AuthService, ConfigService, EventService, PlaylistService, StatusService, ToastrService, - UserService, WebSocketService}; +use crate::services::{AuthService, ConfigService, EventService, PlaylistService, StatusService, StreamsService, ToastrService, UserService, WebSocketService}; pub struct Services { pub auth: Rc, pub config: Rc, pub user: Rc, pub status: Rc, + pub streams: Rc, pub event: Rc, pub playlist: Rc, pub toastr: Rc, @@ -21,6 +21,7 @@ impl Services { let config = Rc::new(ConfigService::new(web_config, Rc::clone(&event))); let auth = Rc::new(AuthService::new()); let status = Rc::new(StatusService::new()); + let streams = Rc::new(StreamsService::new()); let playlist = Rc::new(PlaylistService::new()); let toastr = Rc::new(ToastrService::new()); let user = Rc::new(UserService::new(Rc::clone(&event))); @@ -29,6 +30,7 @@ impl Services { auth, config, status, + streams, event, playlist, user, diff --git a/frontend/src/model/event_message.rs b/frontend/src/model/event_message.rs index 7061a6be7..7bbc3c45a 100644 --- a/frontend/src/model/event_message.rs +++ b/frontend/src/model/event_message.rs @@ -1,5 +1,5 @@ use std::rc::Rc; -use shared::model::{ConfigType, PlaylistUpdateState, StatusCheck}; +use shared::model::{ActiveUserConnectionChange, ConfigType, PlaylistUpdateState, StatusCheck}; use crate::model::BusyStatus; #[derive(Debug, Clone, PartialEq)] @@ -7,7 +7,7 @@ pub enum EventMessage { Unauthorized, ServerError(String), ServerStatus(Rc), - ActiveUser(usize, usize), + ActiveUser(ActiveUserConnectionChange), ActiveProvider(String, usize), ConfigChange(ConfigType), Busy(BusyStatus), diff --git a/frontend/src/model/view_type.rs b/frontend/src/model/view_type.rs index 3e47a1553..e61044d87 100644 --- a/frontend/src/model/view_type.rs +++ b/frontend/src/model/view_type.rs @@ -4,6 +4,7 @@ use shared::error::{info_err, TuliproxError}; const DASHBOARD: &str = "dashboard"; const STATS: &str = "stats"; +const STREAMS: &str = "streams"; const USERS: &str = "users"; const CONFIG: &str = "config"; const PLAYLIST_UPDATE: &str = "playlist_update"; @@ -16,6 +17,7 @@ const PLAYLIST_EPG: &str = "playlist_epg"; pub enum ViewType { Dashboard, Stats, + Streams, Users, Config, PlaylistUpdate, @@ -31,6 +33,7 @@ impl FromStr for ViewType { match s.to_lowercase().as_str() { DASHBOARD => Ok(ViewType::Dashboard), STATS => Ok(ViewType::Stats), + STREAMS => Ok(ViewType::Streams), USERS => Ok(ViewType::Users), CONFIG => Ok(ViewType::Config), PLAYLIST_UPDATE => Ok(ViewType::PlaylistUpdate), @@ -47,6 +50,7 @@ impl fmt::Display for ViewType { let s = match self { ViewType::Dashboard => DASHBOARD, ViewType::Stats => STATS, + ViewType::Streams => STREAMS, ViewType::Users => USERS, ViewType::Config => CONFIG, ViewType::PlaylistUpdate => PLAYLIST_UPDATE, diff --git a/frontend/src/services/mod.rs b/frontend/src/services/mod.rs index c0c3dfa25..25095df33 100644 --- a/frontend/src/services/mod.rs +++ b/frontend/src/services/mod.rs @@ -8,6 +8,7 @@ mod websocket_service; mod toastr_service; mod event_service; mod user_service; +mod streams_service; pub use self::auth_service::*; pub use self::config_service::*; @@ -18,4 +19,5 @@ pub use self::playlist_service::*; pub use self::websocket_service::*; pub use self::toastr_service::*; pub use self::event_service::*; -pub use self::user_service::*; \ No newline at end of file +pub use self::user_service::*; +pub use self::streams_service::*; \ No newline at end of file diff --git a/frontend/src/services/streams_service.rs b/frontend/src/services/streams_service.rs new file mode 100644 index 000000000..dd7d29b9c --- /dev/null +++ b/frontend/src/services/streams_service.rs @@ -0,0 +1,27 @@ +use std::rc::Rc; +use crate::services::{get_base_href, request_get}; +use shared::model::StreamInfo; +use shared::utils::concat_path_leading_slash; + +pub struct StreamsService { + streams_path: String, +} + +impl Default for StreamsService { + fn default() -> Self { + Self::new() + } +} + +impl StreamsService { + pub fn new() -> Self { + let base_href = get_base_href(); + Self { + streams_path: concat_path_leading_slash(&base_href, "api/v1/streams"), + } + } + + pub async fn get_streams_info(&self) -> Result>>, crate::error::Error> { + request_get::>>(&self.streams_path, None, None).await + } +} \ No newline at end of file diff --git a/frontend/src/services/websocket_service.rs b/frontend/src/services/websocket_service.rs index b685d41a3..27158588c 100644 --- a/frontend/src/services/websocket_service.rs +++ b/frontend/src/services/websocket_service.rs @@ -72,8 +72,8 @@ impl WebSocketService { ProtocolMessage::Error(err) => { error!("{err}"); }, - ProtocolMessage::ActiveUserResponse(user_count, connections) => { - event_service.broadcast(EventMessage::ActiveUser(user_count, connections)); + ProtocolMessage::ActiveUserResponse(event) => { + event_service.broadcast(EventMessage::ActiveUser(event)); }, ProtocolMessage::ActiveProviderResponse(user_count, connections) => { event_service.broadcast(EventMessage::ActiveProvider(user_count, connections)); diff --git a/shared/src/model/active_user_connection_change.rs b/shared/src/model/active_user_connection_change.rs new file mode 100644 index 000000000..b67451cd1 --- /dev/null +++ b/shared/src/model/active_user_connection_change.rs @@ -0,0 +1,8 @@ +use crate::model::StreamInfo; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] +pub enum ActiveUserConnectionChange { + Connected(StreamInfo), + Disconnected(String), // addr + Connections(usize, usize) // user_count, connection_count +} diff --git a/shared/src/model/mod.rs b/shared/src/model/mod.rs index ab5535de9..cc8dc0301 100644 --- a/shared/src/model/mod.rs +++ b/shared/src/model/mod.rs @@ -18,6 +18,8 @@ mod search_request; mod webplayer_url_request; mod epg; mod epg_request; +mod stream_info; +mod active_user_connection_change; pub use self::cluster_flags::*; pub use self::playlist::*; @@ -38,3 +40,5 @@ pub use self::search_request::*; pub use self::webplayer_url_request::*; pub use self::epg::*; pub use self::epg_request::*; +pub use self::stream_info::*; +pub use self::active_user_connection_change::*; diff --git a/shared/src/model/playlist.rs b/shared/src/model/playlist.rs index d8d22ba4d..032cd5ee0 100644 --- a/shared/src/model/playlist.rs +++ b/shared/src/model/playlist.rs @@ -158,17 +158,11 @@ pub struct PlaylistItemHeader { #[serde(default)] pub category_id: u32, pub input_name: String, - #[serde(default)] - pub copy: bool, // not original, a copy } impl PlaylistItemHeader { pub fn gen_uuid(&mut self) { - self.uuid = if self.copy { - generate_playlist_uuid(&format!("copy-{}", self.input_name), &self.id, self.item_type, &format!("copy-{}", self.url)) - } else { - generate_playlist_uuid(&self.input_name, &self.id, self.item_type, &self.url) - }; + self.uuid = generate_playlist_uuid(&self.input_name, &self.id, self.item_type, &self.url); } pub const fn get_uuid(&self) -> &UUIDType { &self.uuid @@ -299,8 +293,6 @@ pub struct M3uPlaylistItem { pub epg_channel_id: Option, pub input_name: String, pub item_type: PlaylistItemType, - #[serde(default)] - pub copy: bool, #[serde(skip)] pub t_stream_url: String, #[serde(skip)] @@ -433,8 +425,6 @@ pub struct XtreamPlaylistItem { pub category_id: u32, pub input_name: String, pub channel_no: u32, - #[serde(default)] - pub copy: bool, } impl XtreamPlaylistItem { @@ -595,7 +585,6 @@ impl PlaylistItem { epg_channel_id: header.epg_channel_id.clone(), input_name: header.input_name.to_string(), item_type: header.item_type, - copy: header.copy, t_stream_url: header.url.to_string(), t_resource_url: None, } @@ -655,7 +644,6 @@ impl PlaylistItem { category_id: header.category_id, input_name: header.input_name.to_string(), channel_no: header.chno.parse::().unwrap_or(0), - copy: header.copy, } } @@ -771,4 +759,4 @@ impl PlaylistGroup { { self.channels.iter().filter(|&c| filter(c)).count() } -} \ No newline at end of file +} diff --git a/shared/src/model/status_check.rs b/shared/src/model/status_check.rs index 2c2b36784..bfa4f7b45 100644 --- a/shared/src/model/status_check.rs +++ b/shared/src/model/status_check.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; +use crate::model::StreamInfo; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct StatusCheck { @@ -13,6 +14,7 @@ pub struct StatusCheck { pub cache: Option, pub active_users: usize, pub active_user_connections: usize, + pub active_user_streams: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub active_provider_connections: Option>, } @@ -29,6 +31,7 @@ impl Default for StatusCheck { active_users: 0, active_user_connections: 0, active_provider_connections: None, + active_user_streams: Vec::new(), } } } \ No newline at end of file diff --git a/shared/src/model/stream_info.rs b/shared/src/model/stream_info.rs new file mode 100644 index 000000000..6cbb39d3f --- /dev/null +++ b/shared/src/model/stream_info.rs @@ -0,0 +1,59 @@ +use serde::{Deserialize, Serialize}; +use crate::model::{M3uPlaylistItem, PlaylistEntry, PlaylistItemType, XtreamCluster, XtreamPlaylistItem}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct StreamChannel { + pub virtual_id: u32, + pub provider_id: u32, + pub item_type: PlaylistItemType, + pub cluster: XtreamCluster, + pub group: String, + pub title: String, + pub url: String, +} +impl XtreamPlaylistItem { + pub fn to_stream_channel(&self) -> StreamChannel { + StreamChannel { + virtual_id: self.virtual_id, + provider_id: self.provider_id, + item_type: self.item_type, + cluster: self.xtream_cluster, + group: self.group.clone(), + title: self.title.clone(), + url: self.url.clone(), + } + } +} + +impl M3uPlaylistItem { + pub fn to_stream_channel(&self) -> StreamChannel { + StreamChannel { + virtual_id: self.virtual_id, + provider_id: self.get_provider_id().unwrap_or_default(), + item_type: self.item_type, + cluster: XtreamCluster::try_from(self.item_type).unwrap_or(XtreamCluster::Live), + group: self.group.clone(), + title: self.title.clone(), + url: self.url.clone(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct StreamInfo { + pub username: String, + pub channel: StreamChannel, + pub provider: String, + pub addr: String, +} + +impl StreamInfo { + pub fn new(username: &str, addr: &str, provider: &str, stream_channel: StreamChannel) -> Self { + Self { + username: username.to_string(), + channel: stream_channel, + provider: provider.to_string(), + addr: addr.to_string(), + } + } +} \ No newline at end of file diff --git a/shared/src/model/web_socket.rs b/shared/src/model/web_socket.rs index 0cf677c5a..6af24ac92 100644 --- a/shared/src/model/web_socket.rs +++ b/shared/src/model/web_socket.rs @@ -1,6 +1,6 @@ use std::io; use bytes::Bytes; -use crate::model::{ConfigType, PlaylistUpdateState, StatusCheck}; +use crate::model::{ActiveUserConnectionChange, ConfigType, PlaylistUpdateState, StatusCheck}; use serde::{Deserialize, Serialize}; pub const PROTOCOL_VERSION: u8 = 1; @@ -79,7 +79,7 @@ pub enum ProtocolMessage { ServerError(String), StatusRequest(String), StatusResponse(StatusCheck), - ActiveUserResponse(usize, usize), // user_count, connection count + ActiveUserResponse(ActiveUserConnectionChange), ActiveProviderResponse(String, usize), ConfigChangeResponse(ConfigType), PlaylistUpdateResponse(PlaylistUpdateState),