Refactoring peer table to be more like route table + splitted off API calls

This commit is contained in:
Maxime Van Hees authored and Lee Smet committed 2024-08-26 13:18:25 +02:00
1 parent b1dbba96e1
commit 109cc80ca0
3 files changed
+151 -164

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
use std::net::SocketAddr;
pub async fn get_peers(
server_addr: SocketAddr,
) -> Result<Vec<mycelium::peer_manager::PeerStats>, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin/peers");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<Vec<mycelium::peer_manager::PeerStats>>().await {
Err(e) => Err(e),
Ok(peers) => Ok(peers),
},
}
}
pub async fn get_selected_routes(
server_addr: SocketAddr,
) -> Result<Vec<mycelium_api::Route>, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin/routes/selected");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<Vec<mycelium_api::Route>>().await {
Err(e) => Err(e),
Ok(selected_routes) => Ok(selected_routes),
},
}
}
pub async fn get_fallback_routes(
server_addr: SocketAddr,
) -> Result<Vec<mycelium_api::Route>, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin/routes/fallback");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<Vec<mycelium_api::Route>>().await {
Err(e) => Err(e),
Ok(selected_routes) => Ok(selected_routes),
},
}
}
pub async fn get_node_info(server_addr: SocketAddr) -> Result<mycelium_api::Info, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<mycelium_api::Info>().await {
Err(e) => Err(e),
Ok(node_info) => Ok(node_info),
},
}
}
+99 -163
View File
@@ -1,5 +1,7 @@
#![allow(non_snake_case)]
mod api;
use dioxus::prelude::*;
use mycelium::peer_manager::PeerType;
use std::cmp::Ordering;
@@ -24,56 +26,6 @@ enum Route {
PageNotFound { route: Vec<String> },
}
pub async fn get_peers(
server_addr: SocketAddr,
) -> Result<Vec<mycelium::peer_manager::PeerStats>, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin/peers");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<Vec<mycelium::peer_manager::PeerStats>>().await {
Err(e) => Err(e),
Ok(peers) => Ok(peers),
},
}
}
pub async fn get_selected_routes(
server_addr: SocketAddr,
) -> Result<Vec<mycelium_api::Route>, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin/routes/selected");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<Vec<mycelium_api::Route>>().await {
Err(e) => Err(e),
Ok(selected_routes) => Ok(selected_routes),
},
}
}
pub async fn get_fallback_routes(
server_addr: SocketAddr,
) -> Result<Vec<mycelium_api::Route>, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin/routes/fallback");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<Vec<mycelium_api::Route>>().await {
Err(e) => Err(e),
Ok(selected_routes) => Ok(selected_routes),
},
}
}
pub async fn get_node_info(server_addr: SocketAddr) -> Result<mycelium_api::Info, reqwest::Error> {
let request_url = format!("http://{server_addr}/api/v1/admin");
match reqwest::get(&request_url).await {
Err(e) => Err(e),
Ok(resp) => match resp.json::<mycelium_api::Info>().await {
Err(e) => Err(e),
Ok(node_info) => Ok(node_info),
},
}
}
fn main() {
// Init logger
dioxus_logger::init(Level::INFO).expect("failed to init logger");
@@ -115,7 +67,7 @@ fn Sidebar() -> Element {
#[component]
fn Home() -> Element {
let fetched_node_info = use_resource(move || get_node_info(SERVER_ADDR));
let fetched_node_info = use_resource(move || api::get_node_info(SERVER_ADDR));
match &*fetched_node_info.read_unchecked() {
Some(Ok(info)) => rsx! {
div { class: "node-info",
@@ -131,12 +83,11 @@ fn Home() -> Element {
#[component]
fn Peers() -> Element {
rsx! {
div { class: "peers",
h2 { "Peers" }
{ PeersTable() }
}
Outlet::<Route> {}
let fetched_peers = use_resource(move || api::get_peers(SERVER_ADDR));
match &*fetched_peers.read_unchecked() {
Some(Ok(peers)) => rsx! { {PeersTable(peers.clone()) } },
Some(Err(e)) => rsx! { div { "An error has occurred while fetching the peers: {e}" } },
None => rsx! { div { "Loading peers..." } },
}
}
@@ -144,11 +95,14 @@ fn Peers() -> Element {
fn Routes() -> Element {
rsx! {
SelectedRoutesTable {}
div { class: "fallback-routes",
h2 { "Fallback Routes" }
{ FallbackRoutesTable() }
}
// FallbackRoutesTable {}
FallbackRoutesTable {}
}
}
#[component]
fn PageNotFound(route: Vec<String>) -> Element {
rsx! {
p { "Page not found"}
}
}
@@ -187,21 +141,41 @@ enum SortDirection {
Descending,
}
#[component]
fn PeersTable() -> Element {
let fetched_peers = use_resource(move || get_peers(SERVER_ADDR));
fn sort_peers(
peers: &mut [mycelium::peer_manager::PeerStats],
column: &str,
direction: &SortDirection,
) {
peers.sort_by(|a, b| {
let cmp = match column {
//"Endpoint" => a.endpoint.cmp(&b.endpoint),
"Type" => PeerTypeWrapper(a.pt.clone()).cmp(&PeerTypeWrapper(b.pt.clone())),
_ => Ordering::Equal,
};
match direction {
SortDirection::Ascending => cmp,
SortDirection::Descending => cmp.reverse(),
}
});
}
fn PeersTable(peers: Vec<mycelium::peer_manager::PeerStats>) -> Element {
let mut current_page = use_signal(|| 0);
let items_per_page = 20;
let mut change_page = move |delta: i32| {
current_page.set(current_page + delta as usize);
};
// Sorting
let mut sort_column = use_signal(|| "Type".to_string());
let mut sort_direction = use_signal(|| SortDirection::Ascending);
let peers_len = peers.len();
let sort_peers = move |column: String| {
let mut change_page = move |delta: i32| {
let cur_page = *current_page.read() as i32;
current_page.set(
(cur_page + delta)
.max(0)
.min((peers_len - 1) as i32 / items_per_page as i32) as usize,
);
};
let mut sort_peers_signal = move |column: String| {
if column == *sort_column.read() {
let new_sort_direction = match *sort_direction.read() {
SortDirection::Ascending => SortDirection::Descending,
@@ -210,55 +184,71 @@ fn PeersTable() -> Element {
sort_direction.set(new_sort_direction);
} else {
sort_column.set(column);
sort_direction.set(SortDirection::Ascending);
sort_direction.set(SortDirection::Descending);
}
};
match &*fetched_peers.read_unchecked() {
Some(Ok(peers)) => {
// Sort peers based on the current sort column and sort direction
let mut sorted_peers = peers.clone();
sorted_peers.sort_by(|a, b| {
let cmp = match sort_column.read().as_str() {
// "Endpoint" => a.endpoint.cmp(&b.endpoint),
"Type" => PeerTypeWrapper(a.pt.clone()).cmp(&PeerTypeWrapper(b.pt.clone())),
_ => std::cmp::Ordering::Equal,
};
match *sort_direction.read() {
SortDirection::Descending => cmp.reverse(),
SortDirection::Ascending => cmp,
}
});
let sorted_peers = use_memo(move || {
let mut sorted = peers.clone();
sort_peers(&mut sorted, &sort_column.read(), &sort_direction.read());
sorted
});
let start = current_page * items_per_page;
let end = (start + items_per_page).min(peers.len());
let current_peers = &sorted_peers[start..end];
rsx! {
{create_peer_table(current_peers, sort_column, sort_direction, sort_peers)}
div { class: "pagination",
button {
disabled: *current_page.read() == 0,
onclick: move |_| change_page(-1),
"Previous"
}
span { "Page {current_page + 1}"}
button {
disabled: (current_page + 1) * items_per_page >= peers.len(),
onclick: move |_| change_page(1),
"Next"
let start = current_page * items_per_page;
let end = (start + items_per_page).min(peers_len);
let current_peers = &sorted_peers.read()[start..end];
rsx! {
div { class: "peers-table",
h2 { "Peers" }
table {
thead {
tr {
th {
onclick: move |_| sort_peers_signal("Endpoint".to_string()),
"Endpoint {get_sort_indicator(sort_column, sort_direction, \"Endpoint\".to_string())}"
}
th {
onclick: move |_| sort_peers_signal("Type".to_string()),
"Type {get_sort_indicator(sort_column, sort_direction, \"Type\".to_string())}"
}
th { "Connection State" }
th { "Tx bytes" }
th { "Rx bytes" }
}
}
tbody {
for peer in current_peers {
tr {
td { "{peer.endpoint}" }
td { "{peer.pt}" }
td { "{peer.connection_state}" }
td { "{peer.tx_bytes}" }
td { "{peer.rx_bytes}" }
}
}
}
}
div { class: "pagination",
button {
disabled: *current_page.read() == 0,
onclick: move |_| change_page(-1),
"Previous"
}
span { "Page {current_page + 1}" }
button {
disabled: (current_page + 1) * items_per_page >= peers_len,
onclick: move |_| change_page(1),
"Next"
}
}
}
Some(Err(e)) => rsx! { div { "An error has occurred while fetching peers: {e}" }},
None => rsx! { div { "Loading peers..." }},
}
}
#[component]
fn SelectedRoutesTable() -> Element {
let fetched_selected_routes = use_resource(move || get_selected_routes(SERVER_ADDR));
let fetched_selected_routes = use_resource(move || api::get_selected_routes(SERVER_ADDR));
match &*fetched_selected_routes.read_unchecked() {
Some(Ok(routes)) => {
@@ -271,7 +261,7 @@ fn SelectedRoutesTable() -> Element {
#[component]
fn FallbackRoutesTable() -> Element {
let fetched_fallback_routes = use_resource(move || get_selected_routes(SERVER_ADDR));
let fetched_fallback_routes = use_resource(move || api::get_fallback_routes(SERVER_ADDR));
match &*fetched_fallback_routes.read_unchecked() {
Some(Ok(routes)) => {
@@ -282,13 +272,6 @@ fn FallbackRoutesTable() -> Element {
}
}
#[component]
fn PageNotFound(route: Vec<String>) -> Element {
rsx! {
p { "Page not found"}
}
}
fn get_sort_indicator(
sort_column: Signal<String>,
sort_direction: Signal<SortDirection>,
@@ -304,53 +287,6 @@ fn get_sort_indicator(
}
}
fn create_peer_table(
peers: &[mycelium::peer_manager::PeerStats],
sort_column: Signal<String>,
sort_direction: Signal<SortDirection>,
mut sort_peers: impl FnMut(String) + Clone + 'static,
) -> Element {
rsx! {
table {
thead {
tr {
// th { "Endpoint" }
th {
onclick: {
let mut sort_peers = sort_peers.clone();
move |_| {
sort_peers("Endpoint".to_string());
}
},
"Endpoint {get_sort_indicator(sort_column, sort_direction, \"Endpoint\".to_string())}"
}
th {
onclick: move |_| {
sort_peers("Type".to_string());
},
"Type {get_sort_indicator(sort_column, sort_direction, \"Type\".to_string())}"
}
// th { "Type" }
th { "Connection State" }
th { "Tx bytes" }
th { "Rx bytes" }
}
}
tbody {
for peer in peers {
tr {
td { "{peer.endpoint}" }
td { "{peer.pt}" }
td { "{peer.connection_state}" }
td { "{peer.tx_bytes}" }
td { "{peer.rx_bytes}" }
}
}
}
}
}
}
fn sort_routes(routes: &mut [mycelium_api::Route], column: &str, direction: &SortDirection) {
routes.sort_by(|a, b| {
let cmp = match column {
+1 -1
View File
@@ -112,7 +112,7 @@ pub enum ConnectionState {
}
/// Identification and information/statistics for a specific [`Peer`]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PeerStats {
/// The endpoint of the [`Peer`].