From 30d2da280dd8bbec1d22c98cc7cd69ff5ccead9c Mon Sep 17 00:00:00 2001 From: Maxime Van Hees Date: Tue, 6 Aug 2024 12:04:21 +0200 Subject: [PATCH] created stop signal for breaking out of coroutine loop --- mycelium-ui/Cargo.lock | 1 + mycelium-ui/Cargo.toml | 1 + mycelium-ui/assets/styles.css | 51 ++++++++++++- mycelium-ui/src/components/peers.rs | 111 ++++++++++++++++------------ mycelium-ui/src/main.rs | 15 +++- 5 files changed, 129 insertions(+), 50 deletions(-) diff --git a/mycelium-ui/Cargo.lock b/mycelium-ui/Cargo.lock index 2792b20..0f68b0a 100644 --- a/mycelium-ui/Cargo.lock +++ b/mycelium-ui/Cargo.lock @@ -3063,6 +3063,7 @@ dependencies = [ "dioxus-free-icons", "dioxus-logger", "dioxus-sortable", + "futures-util", "human_bytes", "manganis", "mycelium", diff --git a/mycelium-ui/Cargo.toml b/mycelium-ui/Cargo.toml index f91089d..117f92e 100644 --- a/mycelium-ui/Cargo.toml +++ b/mycelium-ui/Cargo.toml @@ -27,3 +27,4 @@ dioxus-free-icons = { version = "0.8.6", features = [ human_bytes = { version = "0.4.3", features = ["fast"] } tokio = "1.39.2" dioxus-charts = "0.2.0" +futures-util = "0.3.30" diff --git a/mycelium-ui/assets/styles.css b/mycelium-ui/assets/styles.css index d9b902a..b83bf65 100644 --- a/mycelium-ui/assets/styles.css +++ b/mycelium-ui/assets/styles.css @@ -336,10 +336,25 @@ h2 { background-color: #f8f9fa; } +.graph-container { + width: 100%; + max-width: 800px; + margin-bottom: 2rem; +} + +.graph-title { + font-size: 1.2rem; + font-weight: 700; + margin-bottom: 1rem; + color: var(--secondary-color); + text-align: center; +} + .expanded-content { - padding: 1rem; + padding: 2rem; display: flex; - justify-content: space-between; + /* justify-content: space-between; */ + flex-direction: column; align-items: center; } @@ -348,13 +363,43 @@ h2 { font-size: 0.9rem; } +/* Style for both Tx and Rx charts */ +.expanded-content svg { + margin-bottom: 2rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; + overflow: hidden; +} + +/* Style for chart lines */ +.expanded-content path { + stroke-width: 3; +} + +/* Style for Tx bytes chart */ +.graph-container:nth-child(1) path { + stroke: #3498db; +} + +/* Style for Rx bytes chart */ +.graph-container:nth-child(2) path { + stroke: #2ecc71; +} + .close-button { background-color: var(--primary-color); color: white; border: none; - padding: 0.5rem 1rem; + padding: 0.75rem 1.5rem; + font-size: 1rem; cursor: pointer; transition: background-color 0.3s ease; + border-radius: 4px; + margin-top: 1rem; +} + +.close-button:hover { + background-color: #2980b9; } .close-button:hover { diff --git a/mycelium-ui/src/components/peers.rs b/mycelium-ui/src/components/peers.rs index 7c7c24a..89fa861 100644 --- a/mycelium-ui/src/components/peers.rs +++ b/mycelium-ui/src/components/peers.rs @@ -1,7 +1,10 @@ use crate::get_sort_indicator; -use crate::{api, PeerSignalMapping, SearchState, ServerAddress, SortDirection}; +use crate::{ + api, PeerSignalMapping, SearchState, ServerAddress, SortDirection, StopFetchingPeerSignal, +}; use dioxus::prelude::*; use dioxus_charts::LineChart; +use futures_util::StreamExt; use human_bytes::human_bytes; use mycelium::peer_manager::{PeerStats, PeerType}; use std::{ @@ -18,33 +21,43 @@ pub fn Peers() -> Element { let error = use_signal(|| None::); // Mapping between peer and their corresponding signal let peer_signal_map = use_context::>(); + // Set to false when user is on Peers page. Changes to true when visiting any other page of the + // application + let stop_fetching_signal = use_context::>().read().0; + println!("current value stop_fetching_signal: {stop_fetching_signal}"); - let _: Coroutine<()> = use_coroutine(|_rx: UnboundedReceiver<_>| { + let _: Coroutine<()> = use_coroutine(|_rx| { async move { to_owned![server_addr, error, peer_signal_map]; loop { - tokio::time::sleep(tokio::time::Duration::from_millis(REFRESH_RATE_MS)).await; - match api::get_peers(server_addr).await { - Ok(fetched_peers) => { - let mut peer_signal_map = peer_signal_map.write(); - // populate the peer_signal_map - for peer in fetched_peers { - peer_signal_map - .0 - .entry(peer.endpoint) - .or_insert(use_signal(|| peer.clone())) // ret. mut ref to signal - .set(peer); // set signal (each time, so with new rx/tx data incl.) - } + if !stop_fetching_signal { + println!("Starting loop"); + tokio::time::sleep(tokio::time::Duration::from_millis(REFRESH_RATE_MS)).await; + match api::get_peers(server_addr).await { + Ok(fetched_peers) => { + let mut peer_signal_map = peer_signal_map.write(); + // populate the peer_signal_map + for peer in fetched_peers { + peer_signal_map + .0 + .entry(peer.endpoint) + .or_insert(use_signal(|| peer.clone())) + .set(peer); // set signal (each time, so with new rx/tx data incl.) + } - error.set(None); - } - Err(e) => { - eprintln!("Error fetching peers: {}", e); - error.set(Some(format!( - "An error has occurred while fetching peers: {}", - e - ))) + error.set(None); + } + Err(e) => { + eprintln!("Error fetching peers: {}", e); + error.set(Some(format!( + "An error has occurred while fetching peers: {}", + e + ))) + } } + } else { + println!("breaking out of loop"); + break; } } } @@ -54,13 +67,12 @@ pub fn Peers() -> Element { if let Some(err) = error.read().as_ref() { div { class: "error-message", "{err}" } } else { - PeersTable { } // no arg reuiqred --> we use (global shared) context now + PeersTable { } } } } #[component] -// fn PeersTable(peer_signal_map: HashMap>) -> Element { fn PeersTable() -> Element { let mut current_page = use_signal(|| 0); let items_per_page = 20; @@ -355,29 +367,36 @@ fn ExpandedPeerRow(peer: Signal, on_close: EventHandler<()>) -> Eleme tr { class: "expanded-row", td { colspan: "7", div { class: "expanded-content", - // Tx chart - LineChart { - show_grid: false, - padding_top: 80, - padding_left: 100, - padding_right: 80, - padding_bottom: 80, - label_interpolation: (|v| human_bytes(v as f64).to_string()) as fn(f32)-> String, - series: vec![tx_data.read().to_vec()], - labels: labels.clone(), - series_labels: vec!["Tx Bytes/s".into()], + div { class: "graph-container", + // Tx chart + div { class: "graph-title", "Tx Bytes/s" } + LineChart { + show_grid: false, + show_grid_ticks: true, + padding_top: 80, + padding_left: 100, + padding_right: 80, + padding_bottom: 80, + label_interpolation: (|v| human_bytes(v as f64).to_string()) as fn(f32)-> String, + series: vec![tx_data.read().to_vec()], + labels: labels.clone(), + series_labels: vec!["Tx Bytes/s".into()], + } } - // Rx chart - LineChart { - show_grid: false, - padding_top: 80, - padding_left: 100, - padding_right: 80, - padding_bottom: 80, - label_interpolation: (|v| human_bytes(v as f64).to_string()) as fn(f32)-> String, - series: vec![rx_data.read().clone()], - labels: labels.clone(), - series_labels: vec!["Rx Bytes/s".into()], + div { class: "graph-container", + // Rx chart + div { class: "graph-title", "Rx Bytes/s" } + LineChart { + show_grid: false, + padding_top: 80, + padding_left: 100, + padding_right: 80, + padding_bottom: 80, + label_interpolation: (|v| human_bytes(v as f64).to_string()) as fn(f32)-> String, + series: vec![rx_data.read().clone()], + labels: labels.clone(), + series_labels: vec!["Rx Bytes/s".into()], + } } button { class: "close-button", onclick: move |_| on_close.call(()), diff --git a/mycelium-ui/src/main.rs b/mycelium-ui/src/main.rs index 4e39247..673d50e 100644 --- a/mycelium-ui/src/main.rs +++ b/mycelium-ui/src/main.rs @@ -34,9 +34,17 @@ fn App() -> Element { HashMap::>::new(), )) }); + use_context_provider(|| Signal::new(StopFetchingPeerSignal(false))); rsx! { - Router:: {} + Router:: { + config: || { + RouterConfig::default().on_update(|state| { + use_context::>().write().0 = state.current() != Route::Peers {}; + (state.current() == Route::Peers {}).then_some(NavigationTarget::Internal(Route::Peers {})) + }) + } + } } } @@ -61,6 +69,11 @@ struct SearchState { column: String, } +// This signal is used to stop the loop that keeps fetching information about the peers when +// looking at the peers table, e.g. when the user goes back to Home or Routes page. +#[derive(Clone, PartialEq)] +struct StopFetchingPeerSignal(bool); + #[derive(Clone, PartialEq)] struct ServerAddress(SocketAddr);