user edit proxy type

This commit is contained in:
euzu
2025-08-22 10:07:46 +02:00
parent a30f9d420b
commit 8647223302
8 changed files with 315 additions and 162 deletions
+2 -1
View File
@@ -66,4 +66,5 @@
@forward "components/cvs_table";
@forward "components/config/config_view";
@forward "components/tabset";
@forward "components/select";
@forward "components/select";
@forward "components/form";
+43
View File
@@ -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;
}
}
@@ -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;
@@ -27,4 +27,73 @@
border-bottom-right-radius: 0;
}
}
}
}
.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);
// }
// }
//}
+16 -16
View File
@@ -2,9 +2,9 @@
macro_rules! config_field_optional {
($config:expr, $label:expr, $field:ident) => {
html! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<label>{$label}</label>
<span class="tp__config-field__value">
<span class="tp__form-field__value">
{
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! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<label>{$label}</label>
<span class="tp__config-field__value">
<span class="tp__form-field__value">
<$crate::app::components::HideContent content={$config.$field.as_ref().map_or_else(String::new, |content| content.clone())}></$crate::app::components::HideContent>
</span>
</div>
@@ -35,9 +35,9 @@ macro_rules! config_field_optional_hide {
macro_rules! config_field_hide {
($config:expr, $label:expr, $field:ident) => {
html! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<label>{$label}</label>
<span class="tp__config-field__value">
<span class="tp__form-field__value">
<$crate::app::components::HideContent content={$config.$field.clone()}></$crate::app::components::HideContent>
</span>
</div>
@@ -49,7 +49,7 @@ macro_rules! config_field_hide {
macro_rules! config_field_bool {
($config:expr, $label:expr, $field:ident) => {
html! {
<div class="tp__config-field tp__config-field__bool">
<div class="tp__form-field tp__form-field__bool">
<label>{$label}</label>
<$crate::app::components::ToggleSwitch value={$config.$field} readonly={true} />
</div>
@@ -61,7 +61,7 @@ macro_rules! config_field_bool {
macro_rules! config_field_bool_empty {
($label:expr) => {
html! {
<div class="tp__config-field tp__config-field__bool">
<div class="tp__form-field tp__form-field__bool">
<label>{$label}</label>
<$crate::app::components::ToggleSwitch value={false} readonly={true} />
</div>
@@ -73,9 +73,9 @@ macro_rules! config_field_bool_empty {
macro_rules! config_field {
($config:expr, $label:expr, $field:ident) => {
html! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<label>{$label}</label>
<span class="tp__config-field__value">{$config.$field.to_string()}</span>
<span class="tp__form-field__value">{$config.$field.to_string()}</span>
</div>
}
};
@@ -85,7 +85,7 @@ macro_rules! config_field {
macro_rules! config_field_child {
($label:expr, $body:block) => {
html! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<label>{$label}</label>
{ $body }
</div>
@@ -97,9 +97,9 @@ macro_rules! config_field_child {
macro_rules! config_field_empty {
($label:expr) => {
html! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<label>{$label}</label>
<span class="tp__config-field__value"></span>
<span class="tp__form-field__value"></span>
</div>
}
};
@@ -116,7 +116,7 @@ macro_rules! edit_field_text_option {
(@inner $instance:expr, $label:expr, $field:ident, $hidden:expr) => {{
let instance = $instance.clone();
html! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<$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! {
<div class="tp__config-field tp__config-field__text">
<div class="tp__form-field tp__form-field__text">
<$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! {
<div class="tp__config-field tp__config-field__bool">
<div class="tp__form-field tp__form-field__bool">
<label>{$label}</label>
<$crate::app::components::ToggleSwitch
value={instance.borrow().$field}
@@ -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! {
<Card>
<h1>{translate.t(LABEL_CONTENT_SECURITY_POLICY)}</h1>
{ config_field_bool_empty!(translate.t(LABEL_ENABLED)) }
{ config_field_empty!(translate.t(LABEL_CONTENT_SECURITY_POLICY_CUSTOM_ATTRIBUTES)) }
</Card>
}
};
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! {
<Card>
<h1>{translate.t(LABEL_CONTENT_SECURITY_POLICY)}</h1>
{ config_field_bool!(csp, translate.t(LABEL_ENABLED), enabled) }
{ config_field_child!(translate.t(LABEL_CONTENT_SECURITY_POLICY_CUSTOM_ATTRIBUTES), {
html! {
<div class="tp__config-view__tags">
{
if let Some(custom) = &csp.custom_attributes {
html! { for custom.iter().map(|a| html! { <Chip label={a.clone()} /> }) }
} else {
html! {}
}
}
</div>
}
}) }
</Card>
},
None => render_empty_csp()
}
}
}
};
let render_auth = |web_ui: &WebUiConfigDto| html! {
{
match web_ui.auth.as_ref() {
Some(auth) => html!{
<Card>
<h1>{translate.t(LABEL_AUTH)}</h1>
{ 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) }
</Card>
},
None => render_empty_auth(),
}}
};
html! {
<div class="tp__webui-config-view tp__config-view-page">
<div class="tp__webui-config-config-view__body tp__config-view-page__body">
@@ -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! {
<div class="tp__config-view__tags">
{
if let Some(custom) = &csp.custom_attributes {
html! { for custom.iter().map(|a| html! { <Chip label={a.clone()} /> }) }
} else {
html! {}
}
}
</div>
}
}) }
</>
},
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) }
<Card>
<h1>{translate.t(LABEL_AUTH)}</h1>
{
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(),
}}
</Card>
{ render_csp(web_ui) }
{ render_auth(web_ui) }
</>
}
} else {
@@ -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<ProxyType>,
}
#[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! {
// <Chip class={ format!("tp__proxy-type__reverse tp__proxy-type__reverse-{} active", class_sfx) } label={translate.t(label)} />
// }
// } else {
// html!{
// <Chip class={ format!("tp__proxy-type__redirect tp__proxy-type__redirect-{}", class_sfx)} label={translate.t(label)} />
// }
// }
// };
//
// 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! { <Chip label={translate.t("LABEL.REVERSE")} class={"tp__proxy-type__reverse"} /> }
// } else {
// html! {
// <div class="tp__proxy-type__mixed">
// { render_chip(live_flag, "live", "LABEL.LIVE_SHORT") }
// { render_chip(vod_flag, "vod", "LABEL.VOD_SHORT") }
// { render_chip(series_flag, "series", "LABEL.SERIES_SHORT" ) }
// </div>
// }
// }
// }
// ProxyType::Redirect => html! {
// <Chip label={translate.t("LABEL.REDIRECT")} class={"tp__proxy-type__redirect"} />
// },
// }
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! {
<div class="tp__proxy-type-input">
<span class={"tp__chip"} onclick={handle_redirect_click}>
<span class="tp__chip__label">{ translate.t("LABEL.REDIRECT") }</span>
<span onclick={handle_redirect_click} class={classes!("tp__chip", "tp__proxy-type-input__redirect", if redirect {"active"} else {""})}>
<span>{ translate.t("LABEL.REDIRECT") }</span>
</span>
<span class={"tp__chip"}>
<span class="tp__chip__label">{ translate.t("LABEL.REVERSE") }</span>
<span class={"tp__chip__group"}>
<span class="tp__chip__label">{ translate.t("LABEL.LIVE") }</span>
<span class="tp__chip__label">{ translate.t("LABEL.VIDEO") }</span>
<span class="tp__chip__label">{ translate.t("LABEL.SERIES") }</span>
<span class={classes!("tp__chip", "tp__chip__group", "tp__proxy-type-input__reverse" , if reverse {"active"} else {""})}>
<span onclick={handle_reverse_click}>{ translate.t("LABEL.REVERSE") }</span>
<span class={"tp__chip__group__sub tp__proxy-type-input__mixed"}>
<span onclick={handle_reverse_live_click} class={classes!("tp__chip", "tp__proxy-type-input__reverse-live", if reverse_live {"active"} else { if reverse {"redirect-active"} else {""} })}>{ translate.t("LABEL.LIVE_SHORT") }</span>
<span onclick={handle_reverse_vod_click} class={classes!("tp__chip", "tp__proxy-type-input__reverse-vod", if reverse_vod {"active"} else {if reverse {"redirect-active"} else {""} })}>{ translate.t("LABEL.VOD_SHORT") }</span>
<span onclick={handle_reverse_series_click} class={classes!("tp__chip", "tp__proxy-type-input__reverse-series", if reverse_series {"active"} else {if reverse {"redirect-active"} else {""} })}>{ translate.t("LABEL.SERIES_SHORT") }</span>
</span>
</span>
</div>
@@ -89,8 +89,12 @@ pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html {
{ config_field_child!(translate.t("LABEL.PROXY"), {
html! {
<ProxyTypeInput value={props.user.as_ref().map_or_else(|| ProxyType::Reverse(None), |u| u.credentials.proxy)} />
<ProxyTypeInput value={props.user.as_ref().map_or_else(|| ProxyType::Reverse(None), |u| u.credentials.proxy)}
onchange={Callback::from(|proxy_type: ProxyType|
warn!("Proxy Type: {:?}", proxy_type)
)}/>
}})}
<label>{ translate.t("LABEL.PROXY") }</label>
<span>{"TODO"}</span>