From ff9551713b09dc8e032ab8a1a93408d86f88de58 Mon Sep 17 00:00:00 2001 From: Gabriel Viganotti Date: Sun, 15 Dec 2024 16:41:14 -0300 Subject: [PATCH] feat: show loading icon when retrieving list of nodes --- src/app.rs | 13 ++++++------ src/helpers.rs | 14 ++++++++----- src/node_actions.rs | 6 ++++-- src/nodes_list_view.rs | 45 ++++++++++++++++++++++++++---------------- src/stats.rs | 12 ++++++++++- 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/app.rs b/src/app.rs index d62a97a..00bca5d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -166,7 +166,7 @@ impl ImmutableNodeStatus { #[derive(Clone, Copy, Debug)] pub struct ClientGlobalState { // List of nodes instances and their info/state - pub nodes: RwSignal>>, + pub nodes: RwSignal<(bool, HashMap>)>, // Flag to enable/disable nodes' logs stream pub logs_stream_on_for: RwSignal>, // Flag to enable/disable nodes' metrics charts update @@ -209,7 +209,7 @@ pub fn App() -> impl IntoView { // Provide context to manage all client side states that need to be used globally provide_context(ClientGlobalState { - nodes: RwSignal::new(HashMap::default()), + nodes: RwSignal::new((false, HashMap::default())), logs_stream_on_for: RwSignal::new(None), metrics_update_on_for: RwSignal::new(None), latest_bin_version: RwSignal::new(None), @@ -282,14 +282,15 @@ fn spawn_nodes_list_polling() { .update(|b| *b = info.batch_in_progress); // first let's get rid of those removed remotely - context.nodes.update(|cx_nodes| { + context.nodes.update(|(loaded, cx_nodes)| { + *loaded = true; cx_nodes.retain(|id, node_info| { node_info.read_untracked().status.is_creating() || info.nodes.contains_key(id) }) }); // let's now update those with new values - context.nodes.with_untracked(|cx_nodes| { + context.nodes.with_untracked(|(_, cx_nodes)| { for (id, cn) in cx_nodes { if let Some(updated) = info.nodes.get(id) { if cn.read_untracked() != *updated { @@ -301,9 +302,9 @@ fn spawn_nodes_list_polling() { // we can add any new node created remotely, perhaps by another instance of the app info.nodes .into_iter() - .filter(|(id, _)| !context.nodes.read_untracked().contains_key(id)) + .filter(|(id, _)| !context.nodes.read_untracked().1.contains_key(id)) .for_each(|(id, new_node)| { - context.nodes.update(|nodes| { + context.nodes.update(|(_, nodes)| { let _ = nodes.insert(id.clone(), RwSignal::new(new_node)); }) }); diff --git a/src/helpers.rs b/src/helpers.rs index dc617ea..b7fb915 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -51,7 +51,9 @@ pub async fn add_node_instances( ..Default::default() }; context.nodes.update(|items| { - items.insert(tmp_container_id.clone(), RwSignal::new(tmp_container)); + items + .1 + .insert(tmp_container_id.clone(), RwSignal::new(tmp_container)); }); if count > 1 { @@ -65,7 +67,7 @@ pub async fn add_node_instances( ) .await?; context.nodes.update(|items| { - items.remove(&tmp_container_id); + items.1.remove(&tmp_container_id); }); context.batch_in_progress.update(|info| { if let Some(b) = info { @@ -82,8 +84,10 @@ pub async fn add_node_instances( } else { let node_info = create_node_instance(port, metrics_port, rewards_addr, auto_start).await?; context.nodes.update(|items| { - items.remove(&tmp_container_id); - items.insert(node_info.container_id.clone(), RwSignal::new(node_info)); + items.1.remove(&tmp_container_id); + items + .1 + .insert(node_info.container_id.clone(), RwSignal::new(node_info)); }); }; @@ -97,7 +101,7 @@ pub async fn remove_node_instance(container_id: ContainerId) -> Result<(), Serve delete_node_instance(container_id.clone()).await?; context.nodes.update(|nodes| { - nodes.remove(&container_id); + nodes.1.remove(&container_id); }); Ok(()) diff --git a/src/node_actions.rs b/src/node_actions.rs index 3f40801..015f626 100644 --- a/src/node_actions.rs +++ b/src/node_actions.rs @@ -164,6 +164,7 @@ pub fn NodesActionsView() -> impl IntoView { context .nodes .read() + .1 .keys() .for_each(|id| { selected.insert(id.clone()); @@ -175,7 +176,7 @@ pub fn NodesActionsView() -> impl IntoView { class=move || { if is_selecting_nodes() { "hidden" - } else if context.nodes.read().is_empty() { + } else if context.nodes.read().1.is_empty() { "btn-disabled btn-manage-nodes-action" } else { "btn-manage-nodes-action" @@ -204,7 +205,7 @@ pub fn NodesActionsView() -> impl IntoView { class=move || { if is_selecting_nodes() { "hidden" - } else if context.nodes.read().is_empty() { + } else if context.nodes.read().1.is_empty() { "btn-disabled btn-manage-nodes-action" } else { "btn-manage-nodes-action" @@ -620,6 +621,7 @@ fn ActionsOnSelected(show_actions_menu: RwSignal) -> impl IntoView { let nodes = context .nodes .read_untracked() + .1 .values() .filter(|n| selected.contains(&n.read_untracked().container_id)) .cloned() diff --git a/src/nodes_list_view.rs b/src/nodes_list_view.rs index 70d5925..df256ef 100644 --- a/src/nodes_list_view.rs +++ b/src/nodes_list_view.rs @@ -16,37 +16,48 @@ use leptos::{logging, prelude::*, task::spawn_local}; #[component] pub fn NodesListView() -> impl IntoView { - // we use the context to switch on/off the streaming of logs let context = expect_context::(); // this signal keeps the reactive list of log entries let (logs, set_logs) = signal(Vec::new()); let (chart_data, set_chart_data) = signal((vec![], vec![])); let (is_render_chart, set_render_chart) = signal(false); - // we display the instances sorted by creation time, newest to oldest + // we display the instances sorted with the currently selected strategy let sorted_nodes = Memo::new(move |_| { - let mut sorted = context.nodes.get().into_iter().collect::>(); + let mut sorted = context.nodes.get().1.into_iter().collect::>(); context.nodes_sort_strategy.read().sort_items(&mut sorted); sorted }); view! { -
- + + Loading... +
+ } + } + > - - }.into_view() } +
+ + + - - - -
+ }.into_view() } + > + + +
+ +