From 8647223302cf839557a4f482ca77dec310c19125 Mon Sep 17 00:00:00 2001 From: euzu Date: Fri, 22 Aug 2025 10:07:46 +0200 Subject: [PATCH] user edit proxy type --- frontend/scss/app/_component.scss | 3 +- frontend/scss/app/components/_form.scss | 43 +++++ .../app/components/config/_config_view.scss | 42 ----- .../app/components/userlist/_proxy_type.scss | 71 +++++++- frontend/src/app/components/config/macros.rs | 32 ++-- .../components/config/webui_config_view.rs | 114 ++++++------ .../components/userlist/proxy_type_input.rs | 166 +++++++++++++----- .../userlist/proxy_user_credentials_form.rs | 6 +- 8 files changed, 315 insertions(+), 162 deletions(-) create mode 100644 frontend/scss/app/components/_form.scss diff --git a/frontend/scss/app/_component.scss b/frontend/scss/app/_component.scss index e1fd27f94..f6cedb9fc 100644 --- a/frontend/scss/app/_component.scss +++ b/frontend/scss/app/_component.scss @@ -66,4 +66,5 @@ @forward "components/cvs_table"; @forward "components/config/config_view"; @forward "components/tabset"; -@forward "components/select"; \ No newline at end of file +@forward "components/select"; +@forward "components/form"; \ No newline at end of file diff --git a/frontend/scss/app/components/_form.scss b/frontend/scss/app/components/_form.scss new file mode 100644 index 000000000..893b9272d --- /dev/null +++ b/frontend/scss/app/components/_form.scss @@ -0,0 +1,43 @@ +@use '../../size'; + +.tp__form-field { + display: flex; + gap: var(--gap-default); + box-sizing: border-box; + min-width: size.$config-field-min-width; + + &__text { + flex-flow: column; + } + + &__value { + display: flex; + flex-flow: row nowrap; + align-items: center; + flex: 1 1 auto; + background-color: var(--input-background-color); + color: var(--modest-text-color); + padding: var(--padding-small) var(--padding-mini); + min-height: 2.5rem; + max-height: 2.5rem; + overflow: hidden; + text-overflow: ellipsis; + border: 1px solid var(--input-border-color); + border-radius: var(--border-radius); + box-sizing: border-box; + white-space: nowrap; + } + + &__bool { + flex-flow: row; + align-items: center; + } + + label { + font-weight: bold; + white-space: nowrap; + vertical-align: middle; + color: var(--text-color); + font-size: 1.1rem; + } +} \ No newline at end of file diff --git a/frontend/scss/app/components/config/_config_view.scss b/frontend/scss/app/components/config/_config_view.scss index 497294275..9a828bc93 100644 --- a/frontend/scss/app/components/config/_config_view.scss +++ b/frontend/scss/app/components/config/_config_view.scss @@ -52,48 +52,6 @@ } } -.tp__config-field { - display: flex; - gap: var(--gap-default); - box-sizing: border-box; - min-width: size.$config-field-min-width; - - &__text { - flex-flow: column; - } - - &__value { - display: flex; - flex-flow: row nowrap; - align-items: center; - flex: 1 1 auto; - background-color: var(--input-background-color); - color: var(--modest-text-color); - padding: var(--padding-small) var(--padding-mini); - min-height: 2.5rem; - max-height: 2.5rem; - overflow: hidden; - text-overflow: ellipsis; - border: 1px solid var(--input-border-color); - border-radius: var(--border-radius); - box-sizing: border-box; - white-space: nowrap; - } - - &__bool { - flex-flow: row; - align-items: center; - } - - label { - font-weight: bold; - white-space: nowrap; - vertical-align: middle; - color: var(--text-color); - font-size: 1.1rem; - } -} - .tp__config-view-page { display: flex; flex-flow: column; diff --git a/frontend/scss/app/components/userlist/_proxy_type.scss b/frontend/scss/app/components/userlist/_proxy_type.scss index 17f33e92f..0b2dc109b 100644 --- a/frontend/scss/app/components/userlist/_proxy_type.scss +++ b/frontend/scss/app/components/userlist/_proxy_type.scss @@ -27,4 +27,73 @@ border-bottom-right-radius: 0; } } -} \ No newline at end of file +} + +.tp__proxy-type-input { + display: flex; + flex-flow: row wrap; + gap: var(--gap-default); + .tp__chip { + cursor: pointer; + &:hover { + border-color: var(--active-color); + } + } + + &__reverse.active, + &__reverse-live.active, + &__reverse-vod.active, + &__reverse-series.active { + color: var(--tag-active-color); + background-color: var(--tag-active-background-color); + border-color: var(--tag-active-border-color); + } + + &__reverse-live.redirect-active, + &__reverse-vod.redirect-active, + &__reverse-series.redirect-active, + &__redirect.active { + color: var(--tag-alt-active-color); + background-color: var(--tag-alt-active-background-color); + border-color: var(--tag-alt-active-border-color); + } + + &__mixed { + display: flex; + flex-flow: row nowrap; + span:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + span:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } +} + +.tp__chip__group { + display: flex; + flex-flow: row nowrap; + gap: var(--gap-default); +} + +// &__sub { +// display: flex; +// flex-flow: row nowrap; +// span:not(:first-child) { +// border-top-left-radius: 0; +// border-bottom-left-radius: 0; +// } +// span:not(:last-child) { +// border-top-right-radius: 0; +// border-bottom-right-radius: 0; +// } +// +// .tp__chip.active { +// background-color: var(--tag-active-background-color); +// color: var(--tag-active-color); +// fill: var(--tag-active-color); +// } +// } +//} \ No newline at end of file diff --git a/frontend/src/app/components/config/macros.rs b/frontend/src/app/components/config/macros.rs index 23822708f..6aec3b774 100644 --- a/frontend/src/app/components/config/macros.rs +++ b/frontend/src/app/components/config/macros.rs @@ -2,9 +2,9 @@ macro_rules! config_field_optional { ($config:expr, $label:expr, $field:ident) => { html! { -
+
- + { match $config.$field.as_ref() { Some(value) => html! { &value }, @@ -21,9 +21,9 @@ macro_rules! config_field_optional { macro_rules! config_field_optional_hide { ($config:expr, $label:expr, $field:ident) => { html! { -
+
- + <$crate::app::components::HideContent content={$config.$field.as_ref().map_or_else(String::new, |content| content.clone())}>
@@ -35,9 +35,9 @@ macro_rules! config_field_optional_hide { macro_rules! config_field_hide { ($config:expr, $label:expr, $field:ident) => { html! { -
+
- + <$crate::app::components::HideContent content={$config.$field.clone()}>
@@ -49,7 +49,7 @@ macro_rules! config_field_hide { macro_rules! config_field_bool { ($config:expr, $label:expr, $field:ident) => { html! { -
+
<$crate::app::components::ToggleSwitch value={$config.$field} readonly={true} />
@@ -61,7 +61,7 @@ macro_rules! config_field_bool { macro_rules! config_field_bool_empty { ($label:expr) => { html! { -
+
<$crate::app::components::ToggleSwitch value={false} readonly={true} />
@@ -73,9 +73,9 @@ macro_rules! config_field_bool_empty { macro_rules! config_field { ($config:expr, $label:expr, $field:ident) => { html! { -
+
- {$config.$field.to_string()} + {$config.$field.to_string()}
} }; @@ -85,7 +85,7 @@ macro_rules! config_field { macro_rules! config_field_child { ($label:expr, $body:block) => { html! { -
+
{ $body }
@@ -97,9 +97,9 @@ macro_rules! config_field_child { macro_rules! config_field_empty { ($label:expr) => { html! { -
+
- +
} }; @@ -116,7 +116,7 @@ macro_rules! edit_field_text_option { (@inner $instance:expr, $label:expr, $field:ident, $hidden:expr) => {{ let instance = $instance.clone(); html! { -
+
<$crate::app::components::input::Input label={$label} hidden={$hidden} @@ -147,7 +147,7 @@ macro_rules! edit_field_text { (@inner $instance:expr, $label:expr, $field:ident, $hidden:expr) => {{ let instance = $instance.clone(); html! { -
+
<$crate::app::components::input::Input label={$label} hidden={$hidden} @@ -170,7 +170,7 @@ macro_rules! edit_field_bool { ($instance:expr, $label:expr, $field:ident) => {{ let instance = $instance.clone(); html! { -
+
<$crate::app::components::ToggleSwitch value={instance.borrow().$field} diff --git a/frontend/src/app/components/config/webui_config_view.rs b/frontend/src/app/components/config/webui_config_view.rs index d0303e68f..3297602f2 100644 --- a/frontend/src/app/components/config/webui_config_view.rs +++ b/frontend/src/app/components/config/webui_config_view.rs @@ -6,6 +6,7 @@ use crate::{ }; use yew::prelude::*; use yew_i18n::use_translation; +use shared::model::WebUiConfigDto; const LABEL_AUTH: &str = "LABEL.AUTH"; const LABEL_ENABLED: &str = "LABEL.ENABLED"; @@ -37,26 +38,75 @@ pub fn WebUiConfigView() -> Html { } }; + let render_empty_csp = || { + html! { + +

{translate.t(LABEL_CONTENT_SECURITY_POLICY)}

+ { config_field_bool_empty!(translate.t(LABEL_ENABLED)) } + { config_field_empty!(translate.t(LABEL_CONTENT_SECURITY_POLICY_CUSTOM_ATTRIBUTES)) } +
+ } + }; + let render_empty = || { html! { <> { config_field_bool_empty!(translate.t(LABEL_ENABLED)) } { config_field_bool_empty!(translate.t(LABEL_USER_UI_ENABLED)) } - { config_field_child!(translate.t(LABEL_CONTENT_SECURITY_POLICY), { - html! { - <> - { config_field_bool_empty!(translate.t(LABEL_ENABLED)) } - { config_field_empty!(translate.t(LABEL_CONTENT_SECURITY_POLICY_CUSTOM_ATTRIBUTES)) } - - } - }) } { config_field_empty!(translate.t(LABEL_PATH)) } { config_field_empty!(translate.t(LABEL_PLAYER_SERVER)) } + { render_empty_csp()} { render_empty_auth() } } }; + let render_csp = |web_ui: &WebUiConfigDto| { + html! { + { + match web_ui.content_security_policy.as_ref() { + Some(csp) => html! { + +

{translate.t(LABEL_CONTENT_SECURITY_POLICY)}

+ { config_field_bool!(csp, translate.t(LABEL_ENABLED), enabled) } + { config_field_child!(translate.t(LABEL_CONTENT_SECURITY_POLICY_CUSTOM_ATTRIBUTES), { + html! { +
+ { + if let Some(custom) = &csp.custom_attributes { + html! { for custom.iter().map(|a| html! { }) } + } else { + html! {} + } + } +
+ } + }) } +
+ }, + None => render_empty_csp() + } + } + } + }; + + let render_auth = |web_ui: &WebUiConfigDto| html! { + { + match web_ui.auth.as_ref() { + Some(auth) => html!{ + +

{translate.t(LABEL_AUTH)}

+ { config_field_bool!(auth, translate.t(LABEL_ENABLED), enabled) } + { config_field!(auth, translate.t(LABEL_ISSUER), issuer) } + { config_field_hide!(auth, translate.t(LABEL_SECRET), secret) } + { config_field!(auth, translate.t(LABEL_TOKEN_TTL_MINS), token_ttl_mins) } + { config_field_optional!(auth, translate.t(LABEL_USERFILE), userfile) } +
+ }, + None => render_empty_auth(), + }} + }; + html! {
@@ -67,54 +117,10 @@ pub fn WebUiConfigView() -> Html { <> { config_field_bool!(web_ui, translate.t(LABEL_ENABLED), enabled) } { config_field_bool!(web_ui, translate.t(LABEL_USER_UI_ENABLED), user_ui_enabled) } - { config_field_child!(translate.t(LABEL_CONTENT_SECURITY_POLICY), { - html! { - match web_ui.content_security_policy.as_ref() { - Some(csp) => html! { - <> - { config_field_bool!(csp, translate.t(LABEL_ENABLED), enabled) } - { config_field_child!(translate.t(LABEL_CONTENT_SECURITY_POLICY_CUSTOM_ATTRIBUTES), { - html! { -
- { - if let Some(custom) = &csp.custom_attributes { - html! { for custom.iter().map(|a| html! { }) } - } else { - html! {} - } - } -
- } - }) } - - }, - None => html! { - <> - { config_field_bool_empty!(translate.t(LABEL_ENABLED)) } - { config_field_empty!(translate.t(LABEL_CONTENT_SECURITY_POLICY_CUSTOM_ATTRIBUTES)) } - - } - } - } - }) } { config_field_optional!(web_ui, translate.t(LABEL_PATH), path) } { config_field_optional!(web_ui, translate.t(LABEL_PLAYER_SERVER), player_server) } - -

{translate.t(LABEL_AUTH)}

- { - match web_ui.auth.as_ref() { - Some(auth) => html!{ - <> - { config_field_bool!(auth, translate.t(LABEL_ENABLED), enabled) } - { config_field!(auth, translate.t(LABEL_ISSUER), issuer) } - { config_field_hide!(auth, translate.t(LABEL_SECRET), secret) } - { config_field!(auth, translate.t(LABEL_TOKEN_TTL_MINS), token_ttl_mins) } - { config_field_optional!(auth, translate.t(LABEL_USERFILE), userfile) } - - }, - None => render_empty_auth(), - }} -
+ { render_csp(web_ui) } + { render_auth(web_ui) } } } else { diff --git a/frontend/src/app/components/userlist/proxy_type_input.rs b/frontend/src/app/components/userlist/proxy_type_input.rs index 4962c9b10..dad2a3292 100644 --- a/frontend/src/app/components/userlist/proxy_type_input.rs +++ b/frontend/src/app/components/userlist/proxy_type_input.rs @@ -1,69 +1,141 @@ -use shared::model::{ProxyType}; +use shared::model::{ClusterFlags, ProxyType}; use yew::prelude::*; use yew_i18n::use_translation; +fn get_flags(pt: ProxyType)-> (bool, bool, bool, bool, bool) { + match pt { + ProxyType::Reverse(flags) => { + let cluster = flags.unwrap_or_else(ClusterFlags::all); + let live_flag = cluster.contains(ClusterFlags::Live); + let vod_flag = cluster.contains(ClusterFlags::Vod); + let series_flag = cluster.contains(ClusterFlags::Series); + (false, true, live_flag, vod_flag, series_flag) + } + ProxyType::Redirect => (true, false, false, false, false), + } +} + #[derive(Properties, Clone, PartialEq, Debug)] pub struct ProxyTypeInputProps { pub value: ProxyType, + #[prop_or_default] + pub onchange: Callback, } #[function_component] pub fn ProxyTypeInput(props: &ProxyTypeInputProps) -> Html { let translate = use_translation(); - // - // let render_chip = |flag: bool, class_sfx: &str, label: &str, | -> Html { - // if flag { - // html! { - // - // } - // } else { - // html!{ - // - // } - // } - // }; - // - // match props.value { - // ProxyType::Reverse(flags) => { - // let cluster = flags.unwrap_or_else(ClusterFlags::all); - // let live_flag = cluster.contains(ClusterFlags::Live); - // let vod_flag = cluster.contains(ClusterFlags::Vod); - // let series_flag = cluster.contains(ClusterFlags::Series); - // if live_flag && vod_flag && series_flag { - // html! { } - // } else { - // html! { - //
- // { render_chip(live_flag, "live", "LABEL.LIVE_SHORT") } - // { render_chip(vod_flag, "vod", "LABEL.VOD_SHORT") } - // { render_chip(series_flag, "series", "LABEL.SERIES_SHORT" ) } - //
- // } - // } - // } - // ProxyType::Redirect => html! { - // - // }, - // } + let selections = use_state(|| get_flags(props.value)); - let handle_redirect_click = { - Callback::from(move |_| { - println!("redirect"); + { + let set_selections = selections.clone(); + use_effect_with(props.value, move |pt| { + set_selections.set(get_flags(*pt)); + }); + } + + let handle_change = { + let onchange = props.onchange.clone(); + Callback::from(move |(redirect, _reverse, live, vod, series)| { + if redirect { + onchange.emit(ProxyType::Redirect); + } else { + let cluster_flags = if live && vod && series { + None + } else { + let mut flags = ClusterFlags::empty(); + if live { + flags.insert(ClusterFlags::Live); + } + if vod { + flags.insert(ClusterFlags::Vod); + } + if series { + flags.insert(ClusterFlags::Series); + } + Some(flags) + }; + onchange.emit(ProxyType::Reverse(cluster_flags)); + } }) }; + let handle_redirect_click = { + let set_selections = selections.clone(); + let emit_change = handle_change.clone(); + Callback::from(move |_| { + let new_flags = (true, false, false, false, false); + set_selections.set(new_flags); + emit_change.emit(new_flags); + }) + }; + let handle_reverse_click = { + let set_selections = selections.clone(); + let emit_change = handle_change.clone(); + Callback::from(move |_| { + let new_flags = (false, true, true, true, true); + set_selections.set(new_flags); + emit_change.emit(new_flags); + }) + }; + let handle_reverse_live_click = { + let set_selections = selections.clone(); + let emit_change = handle_change.clone(); + Callback::from(move |_| { + let flags = *set_selections; + let new_flags = if flags.0 { + (false, true, true, true, true) + } else { + (false, true, !flags.2, flags.3, flags.4) + }; + set_selections.set(new_flags); + emit_change.emit(new_flags); + }) + }; + let handle_reverse_vod_click = { + let set_selections = selections.clone(); + let emit_change = handle_change.clone(); + Callback::from(move |_| { + let flags = *set_selections; + let new_flags = if flags.0 { + (false, true, true, true, true) + } else { + (false, true, flags.2, !flags.3, flags.4) + }; + set_selections.set(new_flags); + emit_change.emit(new_flags); + }) + }; + + let handle_reverse_series_click = { + let set_selections = selections.clone(); + let emit_change = handle_change.clone(); + Callback::from(move |_| { + let flags = *set_selections; + let new_flags = if flags.0 { + (false, true, true, true, true) + } else { + (false, true, flags.2, flags.3, !flags.4) + }; + set_selections.set(new_flags); + emit_change.emit(new_flags); + }) + }; + + let (redirect, reverse, reverse_live, reverse_vod, reverse_series) = *selections; + html! {
- - { translate.t("LABEL.REDIRECT") } + + { translate.t("LABEL.REDIRECT") } - - { translate.t("LABEL.REVERSE") } - - { translate.t("LABEL.LIVE") } - { translate.t("LABEL.VIDEO") } - { translate.t("LABEL.SERIES") } + + { translate.t("LABEL.REVERSE") } + + { translate.t("LABEL.LIVE_SHORT") } + { translate.t("LABEL.VOD_SHORT") } + { translate.t("LABEL.SERIES_SHORT") }
diff --git a/frontend/src/app/components/userlist/proxy_user_credentials_form.rs b/frontend/src/app/components/userlist/proxy_user_credentials_form.rs index 699a4e8dc..35f8cbc79 100644 --- a/frontend/src/app/components/userlist/proxy_user_credentials_form.rs +++ b/frontend/src/app/components/userlist/proxy_user_credentials_form.rs @@ -89,8 +89,12 @@ pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html { { config_field_child!(translate.t("LABEL.PROXY"), { html! { - + }})} + {"TODO"}