From f30d2fa2e8275338fb15138ea043bf7e0cd3d715 Mon Sep 17 00:00:00 2001 From: euzu Date: Thu, 28 Aug 2025 14:27:35 +0200 Subject: [PATCH] Sort user table by date --- frontend/public/assets/i18n/en.json | 1 + frontend/src/app/components/cell_value.rs | 48 +++++++++++++++++ frontend/src/app/components/mod.rs | 2 + frontend/src/app/components/table.rs | 2 +- .../src/app/components/userlist/user_table.rs | 29 +++++----- shared/src/model/config/api_user.rs | 54 +++++++++++++++++++ 6 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 frontend/src/app/components/cell_value.rs diff --git a/frontend/public/assets/i18n/en.json b/frontend/public/assets/i18n/en.json index 8ebd73ec8..13796f023 100644 --- a/frontend/public/assets/i18n/en.json +++ b/frontend/public/assets/i18n/en.json @@ -259,6 +259,7 @@ "EPG": "Epg", "PERSIST": "Persist", "ALIASES": "Aliases", + "MAX_CON": "Max Con.", "MAX_CONNECTIONS": "Max Connections", "METHOD": "Method", "EPG_TIMESHIFT": "Epg Timeshift", diff --git a/frontend/src/app/components/cell_value.rs b/frontend/src/app/components/cell_value.rs new file mode 100644 index 000000000..f6a55f533 --- /dev/null +++ b/frontend/src/app/components/cell_value.rs @@ -0,0 +1,48 @@ +use shared::model::{ProxyType, ProxyUserStatus}; + +pub enum CellValue<'a> { + Empty, + Bool(bool), + Status(ProxyUserStatus), + Proxy(ProxyType), + Text(&'a str), + Date(i64) +} + +impl<'a> PartialOrd for CellValue<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl<'a> Ord for CellValue<'a> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (CellValue::Empty, CellValue::Empty) => std::cmp::Ordering::Equal, + (CellValue::Bool(a), CellValue::Bool(b)) => a.cmp(b), + (CellValue::Status(a), CellValue::Status(b)) => a.cmp(b), + (CellValue::Proxy(a), CellValue::Proxy(b)) => a.cmp(b), + (CellValue::Text(a), CellValue::Text(b)) => a.cmp(b), + (CellValue::Date(a), CellValue::Date(b)) => a.cmp(b), + + (CellValue::Empty, _) => std::cmp::Ordering::Less, + (CellValue::Bool(_), CellValue::Empty) => std::cmp::Ordering::Greater, + (CellValue::Bool(_), _) => std::cmp::Ordering::Less, + (CellValue::Status(_), CellValue::Empty | CellValue::Bool(_)) => std::cmp::Ordering::Greater, + (CellValue::Status(_), _) => std::cmp::Ordering::Less, + (CellValue::Proxy(_), CellValue::Empty | CellValue::Bool(_) | CellValue::Status(_)) => std::cmp::Ordering::Greater, + (CellValue::Proxy(_), _) => std::cmp::Ordering::Less, + (CellValue::Text(_), CellValue::Date(_)) => std::cmp::Ordering::Less, + (CellValue::Text(_), _) => std::cmp::Ordering::Greater, + (CellValue::Date(_), _) => std::cmp::Ordering::Greater, + } + } +} + +impl<'a> PartialEq for CellValue<'a> { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == std::cmp::Ordering::Equal + } +} + +impl<'a> Eq for CellValue<'a> {} \ No newline at end of file diff --git a/frontend/src/app/components/mod.rs b/frontend/src/app/components/mod.rs index 3a98b3a2b..0d446989f 100644 --- a/frontend/src/app/components/mod.rs +++ b/frontend/src/app/components/mod.rs @@ -40,6 +40,7 @@ mod number_input; mod date_input; mod role_based_content; mod api_user; +mod cell_value; // pub use self::input::*; // pub use self::menu_item::*; // pub use self::popup_menu::*; @@ -77,5 +78,6 @@ pub use self::accordion_panel::*; pub use self::csv_table::*; pub use self::tabset::*; pub use self::role_based_content::*; +pub use self::cell_value::*; //pub use self::number_input::*; //pub use self::date_input::*; \ No newline at end of file diff --git a/frontend/src/app/components/table.rs b/frontend/src/app/components/table.rs index 409ccaf63..3e53560f2 100644 --- a/frontend/src/app/components/table.rs +++ b/frontend/src/app/components/table.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use yew::prelude::*; use crate::app::components::{NoContent, AppIcon}; -use shared::model::SortOrder; +use shared::model::{SortOrder}; #[derive(Properties, PartialEq)] pub struct TableDefinition { diff --git a/frontend/src/app/components/userlist/user_table.rs b/frontend/src/app/components/userlist/user_table.rs index 28bc5eda6..5337b04fc 100644 --- a/frontend/src/app/components/userlist/user_table.rs +++ b/frontend/src/app/components/userlist/user_table.rs @@ -1,14 +1,11 @@ -use std::borrow::Cow; use crate::app::components::menu_item::MenuItem; use crate::app::components::popup_menu::PopupMenu; -use crate::app::components::{convert_bool_to_chip_style, AppIcon, Chip, HideContent, MaxConnections, - ProxyTypeView, RevealContent, Table, TableDefinition, UserStatus, - UserlistContext, UserlistPage}; +use crate::app::components::{convert_bool_to_chip_style, AppIcon, CellValue, Chip, HideContent, MaxConnections, ProxyTypeView, RevealContent, Table, TableDefinition, UserStatus, UserlistContext, UserlistPage}; use crate::app::context::TargetUser; use crate::model::DialogResult; use crate::services::DialogService; use shared::error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind}; -use shared::model::SortOrder; +use shared::model::{SortOrder}; use shared::utils::{unix_ts_to_str, Substring}; use std::fmt::Display; use std::rc::Rc; @@ -29,7 +26,7 @@ const HEADERS: [&str; 15] = [ "LABEL.TOKEN", "LABEL.PROXY", "LABEL.SERVER", - "LABEL.MAX_CONNECTIONS", + "LABEL.MAX_CON", "LABEL.UI_ENABLED", "LABEL.EPG_TIMESHIFT", "LABEL.CREATED_AT", @@ -37,20 +34,22 @@ const HEADERS: [&str; 15] = [ "LABEL.COMMENT", ]; -fn get_cell_value(user: &TargetUser, col: usize) -> Cow<'_, str> { +fn get_cell_value(user: &TargetUser, col: usize) -> CellValue<'_> { match col { - 1 => Cow::Owned(user.credentials.is_active().to_string()), - 2 => Cow::Owned(user.credentials.status.as_ref().map_or_else(String::new, ToString::to_string)), - 3 => Cow::Borrowed(user.target.as_str()), - 4 => Cow::Borrowed(user.credentials.username.as_str()), - 7 => Cow::Owned(user.credentials.proxy.to_string()), - 8 => Cow::Owned(user.credentials.server.as_ref().map_or_else(String::new, Clone::clone)), - _ => Cow::Owned(String::new()) + 1 => CellValue::Bool(user.credentials.is_active()), + 2 => user.credentials.status.as_ref().map_or(CellValue::Empty, |s| CellValue::Status(*s)), + 3 => CellValue::Text(user.target.as_str()), + 4 => CellValue::Text(user.credentials.username.as_str()), + 7 => CellValue::Proxy(user.credentials.proxy), + 8 => user.credentials.server.as_ref().map_or(CellValue::Empty, |s|CellValue::Text(s)), + 12 => user.credentials.created_at.as_ref().map_or(CellValue::Empty, |d| CellValue::Date(*d)), + 13 => user.credentials.exp_date.as_ref().map_or(CellValue::Empty, |d| CellValue::Date(*d)), + _ => CellValue::Empty, } } fn is_col_sortable(col: usize) -> bool { - matches!(col, 1 | 2 | 3 | 4 | 7 | 8) + matches!(col, 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13) } diff --git a/shared/src/model/config/api_user.rs b/shared/src/model/config/api_user.rs index 628479c84..95421c182 100644 --- a/shared/src/model/config/api_user.rs +++ b/shared/src/model/config/api_user.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use crate::utils::default_as_true; use std::fmt::Display; use std::str::FromStr; @@ -37,6 +38,28 @@ impl PartialEq for ProxyType { impl Eq for ProxyType {} + +impl Ord for ProxyType { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} +impl PartialOrd for ProxyType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + // Some(match (self, other) { + // (ProxyType::Redirect, ProxyType::Redirect) => std::cmp::Ordering::Equal, + // (ProxyType::Reverse(a), ProxyType::Reverse(b)) => { + // let a_flags = a.map_or(0u16, |f| if f.has_full_flags() { 0u16 } else { f.bits() }); + // let b_flags = b.map_or(0u16, |f| if f.has_full_flags() { 0u16 } else { f.bits() }); + // a_flags.cmp(&b_flags) + // } + // (ProxyType::Redirect, _) => std::cmp::Ordering::Less, + // (ProxyType::Reverse(_), _) => std::cmp::Ordering::Greater, + // }) + } +} + impl Hash for ProxyType { fn hash(&self, state: &mut H) { match self { @@ -207,6 +230,37 @@ impl FromStr for ProxyUserStatus { } } +impl Ord for ProxyUserStatus { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // Definiere numerische Werte für die Sortierreihenfolge + let self_value = match self { + ProxyUserStatus::Active => 0, + ProxyUserStatus::Trial => 1, + ProxyUserStatus::Pending => 2, + ProxyUserStatus::Disabled => 3, + ProxyUserStatus::Expired => 4, + ProxyUserStatus::Banned => 5, + }; + + let other_value = match other { + ProxyUserStatus::Active => 0, + ProxyUserStatus::Trial => 1, + ProxyUserStatus::Pending => 2, + ProxyUserStatus::Disabled => 3, + ProxyUserStatus::Expired => 4, + ProxyUserStatus::Banned => 5, + }; + + self_value.cmp(&other_value) + } +} + +impl PartialOrd for ProxyUserStatus { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Default)] #[serde(deny_unknown_fields)] pub struct ProxyUserCredentialsDto {