mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-14 11:52:24 +02:00
User edit form
This commit is contained in:
@@ -189,7 +189,7 @@
|
||||
"RENAME_SETTINGS": "Rename Settings",
|
||||
"PATTERN": "Pattern",
|
||||
"NEW_NAME": "New Name",
|
||||
"SKIP": "Skip",
|
||||
"SKIP": "Skip",
|
||||
"LIVE_STREAM_USE_PREFIX": "Live use prefix",
|
||||
"LIVE_STREAM_WITHOUT_EXTENSION": "Live w/o ext.",
|
||||
"ALIAS": "Alias",
|
||||
@@ -201,12 +201,12 @@
|
||||
"COUNTER": "Counter",
|
||||
"MAPPING_PATH": "Mapping dir",
|
||||
"USER_CONFIG_DIR": "User Config dir",
|
||||
"USER_ACCESS_CONTROL": "User Access Control",
|
||||
"USER_ACCESS_CONTROL": "User Access Control",
|
||||
"SLEEP_TIMER_MINS": "Sleep Timer (mins)",
|
||||
"CONNECT_TIMEOUT_SECS": "Connect Timeout (secs)",
|
||||
"CONNECT_TIMEOUT_SECS": "Connect Timeout (secs)",
|
||||
"CONFIG_HOT_RELOAD": "Config Hot Reload",
|
||||
"CUSTOM_STREAM_RESPONSE_PATH": "Custom Stream Response dir",
|
||||
"REVERSE_PROXY" : "Reverse Proxy",
|
||||
"CUSTOM_STREAM_RESPONSE_PATH": "Custom Stream Response dir",
|
||||
"REVERSE_PROXY": "Reverse Proxy",
|
||||
"WEB_SEARCH": "Web Search",
|
||||
"USER_UI_ENABLED": "User-UI Enabled",
|
||||
"RESOURCE_REWRITE_DISABLED": "Resource Rewrite Disabled",
|
||||
@@ -220,13 +220,13 @@
|
||||
"THROTTLE_KBPS": "Throttle kbps",
|
||||
"FRIENDLY_NAME": "Friendly Name",
|
||||
"MANUFACTURER": "Manufacturer",
|
||||
"MODEL_NAME": "Model Name",
|
||||
"MODEL_NAME": "Model Name",
|
||||
"MODEL_NUMBER": "Model Number",
|
||||
"FIRMWARE_NAME": "Firmware Name",
|
||||
"FIRMWARE_NAME": "Firmware Name",
|
||||
"FIRMWARE_VERSION": "Firmware Version",
|
||||
"DEVICE_TYPE": "Device Type",
|
||||
"DEVICE_TYPE": "Device Type",
|
||||
"DEVICE_UDN": "Device UDN",
|
||||
"TUNER_COUNT": "Tuner Count",
|
||||
"TUNER_COUNT": "Tuner Count",
|
||||
"SOURCE": "Source",
|
||||
"PRIORITY": "Priority",
|
||||
"LOGO_OVERRIDE": "logo Override",
|
||||
@@ -238,12 +238,12 @@
|
||||
"BEST_MATCH_THRESHOLD": "Best Match Threshold",
|
||||
"NORMALIZE_REGEX": "Normalize Regex",
|
||||
"NAME_PREFIX": "Name Prefix",
|
||||
"NAME_PREFIX_SEPARATOR":"Name Prefix Separator",
|
||||
"NAME_PREFIX_SEPARATOR": "Name Prefix Separator",
|
||||
"STRIP": "Strip",
|
||||
"COPY_LINK_TULIPROX_VIRTUAL_ID": "Copy Virtual Id",
|
||||
"COPY_LINK_TULIPROX_WEBPLAYER_URL": "Copy Webplayer Url",
|
||||
"COPY_LINK_PROVIDER_URL": "Copy Provider Url",
|
||||
"CONTENT_SECURITY_POLICY" : "Content Security Policy",
|
||||
"CONTENT_SECURITY_POLICY": "Content Security Policy",
|
||||
"CUSTOM_ATTRIBUTES": "Custom Attributes",
|
||||
"EMPTY": "",
|
||||
"OPTIONS": "Options",
|
||||
@@ -260,12 +260,18 @@
|
||||
"ALIASES": "Aliases",
|
||||
"MAX_CONNECTIONS": "Max Connections",
|
||||
"METHOD": "Method",
|
||||
"EPG_TIMESHIFT": "Epg Timeshift",
|
||||
"CREATED_AT": "Created at",
|
||||
"EXP_DATE": "Exp. date",
|
||||
"UI_ENABLED": "Ui",
|
||||
"EPG_TIMESHIFT": "Epg Timeshift",
|
||||
"CREATED_AT": "Created at",
|
||||
"EXP_DATE": "Exp. date",
|
||||
"UI_ENABLED": "Ui",
|
||||
"COMMENT": "Comment",
|
||||
"STAGED": "Staged"
|
||||
"STAGED": "Staged",
|
||||
"USER_STATUS_ACTIVE": "Active",
|
||||
"USER_STATUS_EXPIRED": "Expired",
|
||||
"USER_STATUS_BANNED": "Banned",
|
||||
"USER_STATUS_TRIAL": "Trial",
|
||||
"USER_STATUS_DISABLED": "Disabled",
|
||||
"USER_STATUS_PENDING": "Pending"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "User group editor"
|
||||
@@ -276,7 +282,7 @@
|
||||
"INVALID_FILETYPE": "Invalid filetype",
|
||||
"TARGET_NOT_EXISTS": "Target does not exist",
|
||||
"CONFIRM_DELETE": "You really want to delete?",
|
||||
"CONFIG_CHANGED": "Configuration on server changed.",
|
||||
"CONFIG_CHANGED": "Configuration on server changed.",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Successfully downloaded",
|
||||
"FAIL": "Failed to download!",
|
||||
|
||||
@@ -66,3 +66,4 @@
|
||||
@forward "components/config/config_view";
|
||||
@forward "components/tabset";
|
||||
@forward "components/form";
|
||||
@forward "components/select";
|
||||
@@ -0,0 +1,45 @@
|
||||
.tp__select {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
gap: 6px;
|
||||
|
||||
&-wrapper {
|
||||
fill: var(--menu-icon-color);
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding-left: var(--padding-mini);
|
||||
border: 1px solid var(--input-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--input-background-color);
|
||||
&:focus-within {
|
||||
border-color: var(--input-focus-border-color);
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.tp__icon-button {
|
||||
.svg-icon {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
label {
|
||||
padding-left: 2px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&__selected {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
@@ -105,33 +105,6 @@ macro_rules! config_field_empty {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! edit_field_text {
|
||||
($instance:expr, $label:expr, $field:ident) => {
|
||||
$crate::edit_field_text!(@inner $instance, $label, $field, false)
|
||||
};
|
||||
($instance:expr, $label:expr, $field:ident, $hidden:expr) => {
|
||||
$crate::edit_field_text!(@inner $instance, $label, $field, $hidden)
|
||||
};
|
||||
(@inner $instance:expr, $label:expr, $field:ident, $hidden:expr) => {{
|
||||
let instance = $instance.clone();
|
||||
html! {
|
||||
<div class="tp__config-field tp__config-field__text">
|
||||
<$crate::app::components::input::Input
|
||||
label={$label}
|
||||
hidden={$hidden}
|
||||
name={stringify!($field)}
|
||||
autocomplete={true}
|
||||
value={instance.borrow().$field.clone()}
|
||||
ontext={Callback::from(move |value: String| {
|
||||
instance.borrow_mut().$field = value;
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! edit_field_text_option {
|
||||
($instance:expr, $label:expr, $field:ident) => {
|
||||
@@ -163,6 +136,33 @@ macro_rules! edit_field_text_option {
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! edit_field_text {
|
||||
($instance:expr, $label:expr, $field:ident) => {
|
||||
$crate::edit_field_text!(@inner $instance, $label, $field, false)
|
||||
};
|
||||
($instance:expr, $label:expr, $field:ident, $hidden:expr) => {
|
||||
$crate::edit_field_text!(@inner $instance, $label, $field, $hidden)
|
||||
};
|
||||
(@inner $instance:expr, $label:expr, $field:ident, $hidden:expr) => {{
|
||||
let instance = $instance.clone();
|
||||
html! {
|
||||
<div class="tp__config-field tp__config-field__text">
|
||||
<$crate::app::components::input::Input
|
||||
label={$label}
|
||||
hidden={$hidden}
|
||||
name={stringify!($field)}
|
||||
autocomplete={true}
|
||||
value={instance.borrow().$field.clone()}
|
||||
ontext={Callback::from(move |value: String| {
|
||||
instance.borrow_mut().$field = value;
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
//<Input label={translate.t("LABEL.PASSWORD")} input_ref={password_ref} input_type="password" name="password" autocomplete={false} onkeydown={handle_key_down.clone()}/>
|
||||
|
||||
#[macro_export]
|
||||
|
||||
@@ -10,9 +10,9 @@ use crate::html_if;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct DropDownOption {
|
||||
id: String,
|
||||
label: String,
|
||||
selected: bool,
|
||||
pub(crate) id: String,
|
||||
pub(crate) label: String,
|
||||
pub(crate) selected: bool,
|
||||
}
|
||||
|
||||
impl DropDownOption {
|
||||
@@ -31,7 +31,6 @@ pub struct DropDownIconButtonProps {
|
||||
pub options: Rc<Vec<DropDownOption>>,
|
||||
#[prop_or_default]
|
||||
pub multi_select: bool,
|
||||
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
|
||||
@@ -35,6 +35,7 @@ mod accordion_panel;
|
||||
mod csv_table;
|
||||
mod config;
|
||||
mod tabset;
|
||||
mod select;
|
||||
// pub use self::input::*;
|
||||
// pub use self::menu_item::*;
|
||||
// pub use self::popup_menu::*;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
use std::rc::Rc;
|
||||
use log::warn;
|
||||
use yew::prelude::*;
|
||||
use crate::app::components::{DropDownIconButton, DropDownOption, Tag, TagList};
|
||||
|
||||
#[derive(Properties, Clone, PartialEq, Debug)]
|
||||
pub struct SelectProps {
|
||||
pub name: String,
|
||||
#[prop_or_default]
|
||||
pub icon: Option<String>,
|
||||
pub onselect: Callback<(String, Vec<String>)>,
|
||||
#[prop_or_default]
|
||||
pub class: String,
|
||||
pub options: Rc<Vec<DropDownOption>>,
|
||||
#[prop_or_default]
|
||||
pub multi_select: bool,
|
||||
}
|
||||
|
||||
fn create_tag_from_str(s: &str) -> Rc<Tag> {
|
||||
Rc::new(Tag { label: s.to_owned(), class: Some("active".to_string()) })
|
||||
}
|
||||
|
||||
fn create_tag_from_option(o: &DropDownOption) -> Rc<Tag> {
|
||||
create_tag_from_str(o.label.as_str())
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Select(props: &SelectProps) -> Html {
|
||||
|
||||
// TODO render for selections !!!
|
||||
|
||||
let selected_options = use_state(|| props.options.as_ref().iter().filter(|o| o.selected)
|
||||
.map(create_tag_from_option)
|
||||
.collect::<Vec<Rc<Tag>>>());
|
||||
{
|
||||
let set_selected_options = selected_options.clone();
|
||||
use_effect_with(props.options.clone(), move |options| {
|
||||
let selections = options.as_ref().iter().filter(|o| o.selected)
|
||||
.map(create_tag_from_option)
|
||||
.collect::<Vec<Rc<Tag>>>();
|
||||
set_selected_options.set(selections);
|
||||
});
|
||||
}
|
||||
|
||||
let handle_options_click = {
|
||||
let onselect = props.onselect.clone();
|
||||
let set_selections = selected_options.clone();
|
||||
Callback::from(move |(name, selections): (String, Vec<String>)| {
|
||||
set_selections.set(selections.iter().map(|s|s.as_str()).map(create_tag_from_str).collect::<Vec<Rc<Tag>>>());
|
||||
onselect.emit((name, selections));
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class={classes!("tp__select", props.class.clone())}>
|
||||
<div class="tp__select-wrapper">
|
||||
<div class="tp__select__selected">
|
||||
<TagList tags={(*selected_options).clone()} />
|
||||
</div>
|
||||
<DropDownIconButton
|
||||
multi_select={props.multi_select}
|
||||
options={props.options.clone()}
|
||||
name={props.name.clone()}
|
||||
icon={props.icon.as_ref().map_or_else(|| "Popup".to_owned(), |i|i.to_string())}
|
||||
onselect={handle_options_click} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,14 @@ pub fn tag_list(props: &TagListProps) -> Html {
|
||||
let tag_state = use_state(|| tags.clone());
|
||||
let new_tag = use_state(String::default);
|
||||
|
||||
{
|
||||
let tag_state = tag_state.clone();
|
||||
use_effect_with(tags.clone(), move |tags| {
|
||||
tag_state.set(tags.clone());
|
||||
|| ()
|
||||
});
|
||||
}
|
||||
|
||||
let on_remove = {
|
||||
let tag_state = tag_state.clone();
|
||||
let on_change = on_change.clone();
|
||||
|
||||
@@ -3,10 +3,11 @@ use std::rc::Rc;
|
||||
use log::warn;
|
||||
use yew::prelude::*;
|
||||
use yew_i18n::use_translation;
|
||||
use shared::model::ProxyUserCredentialsDto;
|
||||
use shared::model::{ProxyUserCredentialsDto, ProxyUserStatus};
|
||||
use crate::app::TargetUser;
|
||||
use crate::{edit_field_bool, edit_field_text, edit_field_text_option};
|
||||
use crate::app::components::TextButton;
|
||||
use crate::app::components::select::Select;
|
||||
use crate::app::components::{convert_bool_to_chip_style, Chip, DropDownOption, TextButton};
|
||||
|
||||
#[derive(Properties, PartialEq, Clone)]
|
||||
pub struct ProxyUserCredentialsFormProps {
|
||||
@@ -16,18 +17,52 @@ pub struct ProxyUserCredentialsFormProps {
|
||||
#[function_component]
|
||||
pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html {
|
||||
let translate = use_translation();
|
||||
let form_state = use_memo(props.user.clone(), |user| Rc::new(RefCell::new(user.as_ref().map_or_else(|| ProxyUserCredentialsDto::default(), |usr| usr.credentials.as_ref().clone()))));
|
||||
let form_state = use_memo(props.user.clone(),
|
||||
|user| RefCell::new(user.as_ref()
|
||||
.map_or_else(|| ProxyUserCredentialsDto::default(), |usr| usr.credentials.as_ref().clone())));
|
||||
|
||||
let proxy_user_status = use_memo(props.user.clone(), |user|
|
||||
vec![
|
||||
ProxyUserStatus::Active,
|
||||
ProxyUserStatus::Expired,
|
||||
ProxyUserStatus::Banned,
|
||||
ProxyUserStatus::Trial,
|
||||
ProxyUserStatus::Disabled,
|
||||
ProxyUserStatus::Pending,
|
||||
].iter().map(|s| DropDownOption {
|
||||
id: s.to_string(),
|
||||
label: format!("LABEL.USER_STATUS_{}", s.to_string().to_uppercase()),
|
||||
selected: user.as_ref().is_some_and(|user| user.credentials.status.as_ref() == Some(s)),
|
||||
}).collect::<Vec<DropDownOption>>()
|
||||
);
|
||||
|
||||
let handle_save_user = {
|
||||
let user = form_state.clone();
|
||||
let user = form_state.clone();
|
||||
Callback::from(move |_| {
|
||||
warn!("{:?}", user.borrow());
|
||||
})
|
||||
};
|
||||
|
||||
let user_active = props.user.as_ref().is_some_and(|u| u.credentials.is_active());
|
||||
|
||||
html! {
|
||||
<div class="tp__proxy-user-credentials-form tp__form-page">
|
||||
<div class="tp__proxy-user-credentials-form__body tp__form-page__body">
|
||||
<div class="tp__config-field tp__config-field__bool">
|
||||
<Chip class={ convert_bool_to_chip_style(user_active) }
|
||||
label={if user_active {translate.t("LABEL.ENABLED")} else { translate.t("LABEL.DISABLED")} }
|
||||
/>
|
||||
</div>
|
||||
<div class="tp__config-field tp__config-field__text">
|
||||
<label>{translate.t("LABEL.STATUS")}</label>
|
||||
<Select name="status"
|
||||
multi_select={false}
|
||||
onselect={Callback::from(move |(_name, selections):(String, Vec<String>)| {
|
||||
warn!("{}", selections.join(", "));
|
||||
})}
|
||||
options={proxy_user_status.clone()}
|
||||
/>
|
||||
</div>
|
||||
{ edit_field_text!(*form_state, translate.t("LABEL.USERNAME"), username) }
|
||||
{ edit_field_text!(*form_state, translate.t("LABEL.PASSWORD"), password, true) }
|
||||
{ edit_field_bool!(*form_state, translate.t("LABEL.USER_UI_ENABLED"), ui_enabled) }
|
||||
@@ -40,7 +75,6 @@ pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html {
|
||||
// pub created_at: Option<i64>,
|
||||
// pub exp_date: Option<i64>,
|
||||
// pub max_connections: u32,
|
||||
// pub status: Option<ProxyUserStatus>,
|
||||
|
||||
{ edit_field_text_option!(*form_state, translate.t("LABEL.COMMENT"), comment) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user