From efc23c2b21ee768bc641f9075f0ea75f17de6e8d Mon Sep 17 00:00:00 2001 From: euzu Date: Fri, 12 Dec 2025 21:16:52 +0100 Subject: [PATCH] Added kick duration for web_ui kick --- CHANGELOG.md | 3 +-- README.md | 2 +- backend/src/api/endpoints/hls_api.rs | 2 +- backend/src/api/endpoints/m3u_api.rs | 2 +- backend/src/api/endpoints/xtream_api.rs | 2 +- backend/src/api/model/active_user_manager.rs | 2 +- frontend/scss/app/components/_collapse_panel.scss | 1 + frontend/scss/app/components/_filter_editor.scss | 3 ++- frontend/scss/app/components/_filter_input.scss | 3 ++- frontend/scss/app/components/_reveal_content.scss | 1 - frontend/src/app/components/cluster_flags_input.rs | 7 ++++++- frontend/src/app/components/custom_dialog.rs | 3 --- frontend/src/app/components/filter/filter_editor.rs | 13 ++++--------- frontend/src/app/components/filter/filter_input.rs | 10 +++++++--- 14 files changed, 28 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e39741eb6..5b473cd83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,8 +35,7 @@ - Added extended debug logging for client requests and ID chain (request/action/virtual) to trace stream resolution. - Fixed xtream series/catchup lookups using the series-info virtual_id so episode requests now keep their own virtual_id/session. - Made cache storage more robust. Incomplete downloads will be deleted from cache. -- `kick_secs` added to config.yaml `web_ui` config. Default 30 seconds, if a user is kicked from the webui, he can't connect for this duration. - +- `kick_secs` added to config.yaml `web_ui` config. Default 30 seconds, if a user is kicked from the webui, they can't connect for this duration. # 3.2.0 (2025-11-14) - Added `name` attribute to Staged Input. diff --git a/README.md b/README.md index 87d58abc6..8f9290ef2 100644 --- a/README.md +++ b/README.md @@ -417,7 +417,7 @@ log: - `content_security_policy`: configure Content-Security-Policy headers. When `enabled` is true, the default directives `default-src 'self'`, `script-src 'self' 'wasm-unsafe-eval' 'nonce-{nonce_b64}'`, and `frame-ancestors 'none'` are applied. Additional directives can be added via `custom-attributes`. Enabling CSP may block external images/logos unless allowed via directives like `img-src`. - `path` is for web_ui path like `/ui` for reverse proxy integration if necessary. - `player_server` optional, if set the server setting is used for the web-ui-player. -- `kick_secs` default 30 seconds, if a user is kicked from the webui, he can't connect for this duration. +- `kick_secs` default 30 seconds, if a user is kicked from the webui, they can't connect for this duration. - `auth` for authentication settings - `enabled` can be deactivated if `enabled` is set to `false`. If not set default is `true`. - `issuer` diff --git a/backend/src/api/endpoints/hls_api.rs b/backend/src/api/endpoints/hls_api.rs index 480304091..d7715b52f 100644 --- a/backend/src/api/endpoints/hls_api.rs +++ b/backend/src/api/endpoints/hls_api.rs @@ -164,7 +164,7 @@ pub(in crate::api) async fn handle_hls_stream_request( CustomVideoStreamType::ChannelUnavailable); let playlist = PLAYLIST_TEMPLATE.replace("{url}", &url); - hls_response(playlist.clone()).into_response() + hls_response(playlist).into_response() } else { axum::http::StatusCode::NOT_FOUND.into_response() } diff --git a/backend/src/api/endpoints/m3u_api.rs b/backend/src/api/endpoints/m3u_api.rs index cd823d5ef..47220ddb0 100644 --- a/backend/src/api/endpoints/m3u_api.rs +++ b/backend/src/api/endpoints/m3u_api.rs @@ -119,7 +119,7 @@ async fn m3u_api_stream( ); let virtual_id = pli.virtual_id; - if app_state.active_users.is_user_blocked_for_stream(stream_req.username, virtual_id).await { + if app_state.active_users.is_user_blocked_for_stream(&user.username, virtual_id).await { return axum::http::StatusCode::BAD_REQUEST.into_response(); } diff --git a/backend/src/api/endpoints/xtream_api.rs b/backend/src/api/endpoints/xtream_api.rs index 74a586512..ed68c58d6 100644 --- a/backend/src/api/endpoints/xtream_api.rs +++ b/backend/src/api/endpoints/xtream_api.rs @@ -255,7 +255,7 @@ async fn xtream_player_api_stream( format!("Failed to read xtream item for stream id {req_virtual_id}") ); let virtual_id = pli.virtual_id; - if app_state.active_users.is_user_blocked_for_stream(stream_req.username, virtual_id).await { + if app_state.active_users.is_user_blocked_for_stream(&user.username, virtual_id).await { return axum::http::StatusCode::BAD_REQUEST.into_response(); } diff --git a/backend/src/api/model/active_user_manager.rs b/backend/src/api/model/active_user_manager.rs index b1b698c3d..428191df1 100644 --- a/backend/src/api/model/active_user_manager.rs +++ b/backend/src/api/model/active_user_manager.rs @@ -507,7 +507,7 @@ impl ActiveUserManager { let now = current_time_secs(); connections.kicked.retain(|_, (expires_at, _)| *expires_at > now); if let Some(username) = connections.key_by_addr.get(addr).cloned() { - let expires_at = now + blocked_secs; + let expires_at = now + blocked_secs.clamp(1,86_400); // max 1 day connections.kicked.insert(username, (expires_at, virtual_id)); } } diff --git a/frontend/scss/app/components/_collapse_panel.scss b/frontend/scss/app/components/_collapse_panel.scss index 87ec82be9..06901478e 100644 --- a/frontend/scss/app/components/_collapse_panel.scss +++ b/frontend/scss/app/components/_collapse_panel.scss @@ -24,6 +24,7 @@ flex: 1 1 auto; white-space: nowrap; text-overflow: ellipsis; + overflow: hidden; } span { pointer-events: none; diff --git a/frontend/scss/app/components/_filter_editor.scss b/frontend/scss/app/components/_filter_editor.scss index d6580658e..fb9897992 100644 --- a/frontend/scss/app/components/_filter_editor.scss +++ b/frontend/scss/app/components/_filter_editor.scss @@ -59,7 +59,8 @@ &-input { height: 20rem; - width: 100rem; + width: 100%; + max-width: 100%; } } diff --git a/frontend/scss/app/components/_filter_input.scss b/frontend/scss/app/components/_filter_input.scss index 716b623e2..f63b1c10c 100644 --- a/frontend/scss/app/components/_filter_input.scss +++ b/frontend/scss/app/components/_filter_input.scss @@ -6,12 +6,13 @@ .tp__input-wrapper { height: 2.5rem; padding: 0 var(--padding-mini); + display: flex; + align-items: center; justify-content: space-between; } &:hover { fill: var(--text-color); - cursor: pointer; filter: brightness(1.2); } diff --git a/frontend/scss/app/components/_reveal_content.scss b/frontend/scss/app/components/_reveal_content.scss index bf78ff627..41c5ffe67 100644 --- a/frontend/scss/app/components/_reveal_content.scss +++ b/frontend/scss/app/components/_reveal_content.scss @@ -8,7 +8,6 @@ &:hover { fill: var(--text-color); - cursor: pointer; filter: brightness(1.2); } diff --git a/frontend/src/app/components/cluster_flags_input.rs b/frontend/src/app/components/cluster_flags_input.rs index 342dcfee1..455370a11 100644 --- a/frontend/src/app/components/cluster_flags_input.rs +++ b/frontend/src/app/components/cluster_flags_input.rs @@ -24,7 +24,12 @@ pub struct ClusterFlagsInputProps { pub fn ClusterFlagsInput(props: &ClusterFlagsInputProps) -> Html { let translate = use_translation(); - let flags = use_state(|| props.value.unwrap_or_else(ClusterFlags::all)); + let flags = use_state(|| { + props.value.unwrap_or_else(|| match props.mode { + ClusterFlagsInputMode::NoneIsAll => ClusterFlags::all(), + ClusterFlagsInputMode::NoneIsNone => ClusterFlags::empty(), + }) + }); { let set_flags = flags.clone(); use_effect_with((props.value, props.mode), move |(val, cmode)| { diff --git a/frontend/src/app/components/custom_dialog.rs b/frontend/src/app/components/custom_dialog.rs index def0805f8..78feaa69e 100644 --- a/frontend/src/app/components/custom_dialog.rs +++ b/frontend/src/app/components/custom_dialog.rs @@ -1,4 +1,3 @@ -use log::debug; use yew::prelude::*; use web_sys::MouseEvent; @@ -28,8 +27,6 @@ pub fn CustomDialog(props: &CustomDialogProps) -> Html { }); } - debug!(" props close {}", props.close_on_backdrop_click); - // Handle backdrop click let on_backdrop_click = { let on_close = props.on_close.clone(); diff --git a/frontend/src/app/components/filter/filter_editor.rs b/frontend/src/app/components/filter/filter_editor.rs index 8353e981a..cc085908b 100644 --- a/frontend/src/app/components/filter/filter_editor.rs +++ b/frontend/src/app/components/filter/filter_editor.rs @@ -25,14 +25,6 @@ pub fn FilterEditor(props: &FilterEditorProps) -> Html { let parsed_filter_state = use_state(|| None); let valid_filter_state = use_state(|| true); - { - let templates = templates_state.clone(); - let cfg_templates = config_ctx.config.as_ref().and_then(|c| c.sources.templates.clone()); - use_effect_with(cfg_templates, move |templ| { - templates.set(templ.clone()); - }); - } - { let templates = templates_state.clone(); let cfg_templates = config_ctx.config.as_ref().and_then(|c| c.sources.templates.clone()); @@ -75,13 +67,16 @@ pub fn FilterEditor(props: &FilterEditorProps) -> Html { let handle_filter_input = { let filter = filter_state.clone(); + let on_filter_change = props.on_filter_change.clone(); Callback::from(move |event: InputEvent| { if let Some(input) = event.target_dyn_into::() { let value = input.value(); if value.is_empty() { filter.set(None); + on_filter_change.emit(None); } else { - filter.set(Some(value)); + filter.set(Some(value.clone())); + on_filter_change.emit(Some(value)); } } }) diff --git a/frontend/src/app/components/filter/filter_input.rs b/frontend/src/app/components/filter/filter_input.rs index 0e042f4b4..34f175cef 100644 --- a/frontend/src/app/components/filter/filter_input.rs +++ b/frontend/src/app/components/filter/filter_input.rs @@ -79,20 +79,24 @@ pub fn FilterInput(props: &FilterInputProps) -> Html { let dialog = dialog.clone(); let current_filter = filter_state.clone(); let handle_filter_edit = handle_filter_edit.clone(); - let handle_templates_edit =handle_templates_edit.clone(); + let handle_templates_edit = handle_templates_edit.clone(); let dialog_actions = dialog_actions.clone(); Callback::from(move |e: MouseEvent| { e.prevent_default(); let current_filter = (*current_filter).clone(); let handle_filter_edit = handle_filter_edit.clone(); - let handle_templates_edit =handle_templates_edit.clone(); + let handle_templates_edit = handle_templates_edit.clone(); let actions = dialog_actions.clone(); let dlg = dialog.clone(); spawn_local(async move { let filter_view = html!{}; - let _result = dlg.content(filter_view, (*actions).clone(), false).await; + let result = dlg.content(filter_view, (*actions).clone(), false).await; + if result == DialogResult::Ok { + // Propagate the updated filter to parent via on_change + // Note: Need access to filter_state's current value and props.on_change here + } }); }) };