peillute/views/
info.rs

1//! System information component for the Peillute application
2//!
3//! This module provides a component for displaying system-wide information,
4//! including network details, logical clock states, and peer connections.
5
6use dioxus::prelude::*;
7
8/// Server function to retrieve the local network address
9#[server]
10async fn get_local_addr() -> Result<String, ServerFnError> {
11    use crate::state::LOCAL_APP_STATE;
12    let state = LOCAL_APP_STATE.lock().await;
13    Ok(state.get_site_addr_as_string())
14}
15
16/// Server function to retrieve the current site ID
17#[server]
18async fn get_site_id() -> Result<String, ServerFnError> {
19    use crate::state::LOCAL_APP_STATE;
20    let state = LOCAL_APP_STATE.lock().await;
21    Ok(state.get_site_id().to_string())
22}
23
24/// Server function to retrieve the list of connected peers
25#[server]
26async fn get_peers() -> Result<Vec<String>, ServerFnError> {
27    use crate::state::LOCAL_APP_STATE;
28    let state = LOCAL_APP_STATE.lock().await;
29    Ok(state.get_cli_peers_addrs_as_string())
30}
31
32/// Server function to retrieve the current Lamport clock value
33#[server]
34async fn get_lamport() -> Result<i64, ServerFnError> {
35    use crate::state::LOCAL_APP_STATE;
36    let state = LOCAL_APP_STATE.lock().await;
37    Ok(*state.get_clock().get_lamport())
38}
39
40/// Server function to retrieve the current vector clock state
41#[server]
42async fn get_vector_clock() -> Result<String, ServerFnError> {
43    use crate::state::LOCAL_APP_STATE;
44    let state = LOCAL_APP_STATE.lock().await;
45    let vector_clock = state.get_clock().get_vector_clock_values();
46    let vector_clock_string = vector_clock
47        .iter()
48        .map(|x| x.to_string())
49        .collect::<Vec<String>>()
50        .join(", ");
51    Ok(vector_clock_string)
52}
53
54/// Server function to retrieve the database path
55#[server]
56async fn get_db_path() -> Result<String, ServerFnError> {
57    let conn = crate::db::DB_CONN.lock().unwrap();
58    let path = conn.path().unwrap();
59    //keep only the name of the file (after the last "/")
60    Ok(path.to_string().split("/").last().unwrap().to_string())
61}
62
63/// Server function to retrieve the number of neighbours in the network
64#[server]
65async fn get_nb_connected_neighbours() -> Result<i64, ServerFnError> {
66    use crate::state::LOCAL_APP_STATE;
67    let state = LOCAL_APP_STATE.lock().await;
68    Ok(state.get_nb_connected_neighbours())
69}
70
71/// Server function to retrieve the number of cli peers
72#[server]
73async fn get_nb_cli_peers() -> Result<i64, ServerFnError> {
74    use crate::state::LOCAL_APP_STATE;
75    let state = LOCAL_APP_STATE.lock().await;
76    Ok(state.get_cli_peers_addrs().len() as i64)
77}
78
79/// Server function to retrieve the list of connected neighbours
80#[server]
81async fn get_connected_neighbours() -> Result<Vec<String>, ServerFnError> {
82    use crate::state::LOCAL_APP_STATE;
83    let state = LOCAL_APP_STATE.lock().await;
84    Ok(state.get_connected_nei_addr_string())
85}
86
87/// Server function to retrieve the list of peer addresses
88#[server]
89async fn get_peer_addrs() -> Result<Vec<String>, ServerFnError> {
90    use crate::state::LOCAL_APP_STATE;
91    let state = LOCAL_APP_STATE.lock().await;
92    Ok(state.get_cli_peers_addrs_as_string())
93}
94
95/// Ask for a snapshot
96#[server]
97async fn ask_for_snapshot() -> Result<(), ServerFnError> {
98    if let Err(e) =
99        crate::control::enqueue_critical(crate::control::CriticalCommands::FileSnapshot).await
100    {
101        return Err(ServerFnError::new(format!(
102            "[SERVER] Failed make the local snapshot: {e}"
103        )));
104    }
105    Ok(())
106}
107
108/// Get the latest snapshot content if any
109#[server]
110async fn get_snapshot_content() -> Result<Option<String>, ServerFnError> {
111    use crate::snapshot::LOCAL_SNAPSHOT_MANAGER;
112    use tokio::fs::File;
113    use tokio::io::AsyncReadExt;
114
115    let maybe_filename = {
116        let state = LOCAL_SNAPSHOT_MANAGER.lock().await;
117        state.path.clone()
118    };
119
120    if let Some(filename) = maybe_filename {
121        let mut file = File::open(&filename).await?;
122        let mut contents = String::new();
123        file.read_to_string(&mut contents).await?;
124        Ok(Some(contents))
125    } else {
126        Ok(None)
127    }
128}
129
130/// System information component
131///
132/// Displays real-time information about the distributed system, including:
133/// - Database info
134/// - Local network address
135/// - Site ID
136/// - Lamport timestamp
137/// - Vector clock state
138/// - Number of connected sites
139/// - List of connected peers
140/// - Snapshot button
141#[component]
142pub fn Info() -> Element {
143    let mut local_addr = use_signal(|| "".to_string());
144    let mut site_id = use_signal(|| "".to_string());
145    let mut peers_addr = use_signal(|| Vec::new());
146    let mut connected_neighbours = use_signal(|| Vec::new());
147    let mut lamport = use_signal(|| 0i64);
148    let mut vector_clock = use_signal(|| "".to_string());
149    let mut nb_neighbours = use_signal(|| 0i64);
150    let mut nb_peers = use_signal(|| 0i64);
151    let mut db_path = use_signal(|| "".to_string());
152    let mut snapshot_content = use_signal(|| None::<String>);
153
154    use_future(move || async move {
155        // Fetch local address
156        if let Ok(data) = get_local_addr().await {
157            local_addr.set(data);
158        } else {
159            // Optional: Handle error, e.g., log or set a default error message
160            local_addr.set("Error fetching local address".to_string());
161        }
162
163        // Fetch site ID
164        if let Ok(data) = get_site_id().await {
165            site_id.set(data);
166        } else {
167            site_id.set("Error fetching site ID".to_string());
168        }
169
170        // Fetch peers
171        if let Ok(data) = get_peers().await {
172            peers_addr.set(data);
173        } // else: peers remains empty or you could set an error state if needed
174
175        // Fetch connected neighbours
176        if let Ok(data) = get_connected_neighbours().await {
177            connected_neighbours.set(data);
178        } // else: connected_neighbours remains empty or you could set an error state if needed
179
180        // Fetch Lamport clock
181        if let Ok(data) = get_lamport().await {
182            lamport.set(data);
183        } // else: lamport remains 0 or handle error
184
185        // Fetch vector clock (example value)
186        if let Ok(data) = get_vector_clock().await {
187            vector_clock.set(data);
188        } // else: vector_clock remains 0 or handle error
189
190        // Fetch number of sites
191        if let Ok(data) = get_nb_connected_neighbours().await {
192            nb_neighbours.set(data);
193        } // else: nb_sites remains 0 or handle error
194
195        // Fetch number of CLI peers
196        if let Ok(data) = get_nb_cli_peers().await {
197            nb_peers.set(data);
198        } // else : nb_peers remains 0 or handle error
199
200        // Fetch database path
201        if let Ok(data) = get_db_path().await {
202            db_path.set(data);
203        } // else: db_path remains "" or handle error
204
205        // Fetch snapshot content
206        if let Ok(data) = get_snapshot_content().await {
207            snapshot_content.set(data);
208        } // else: snapshot_content remains None or handle error
209    });
210
211    rsx! {
212        div { class: "info-panel", // You can style this class with CSS
213            h2 { "System Information" }
214
215            div { class: "info-item",
216                strong { "💾 Database : " }
217                span { "{db_path}" }
218            }
219
220            div { class: "info-item",
221                strong { "🌐 Site Address: " }
222                span { "{local_addr}" }
223            }
224            div { class: "info-item",
225                strong { "🆔 Site ID: " }
226                span { "{site_id}" }
227            }
228            div { class: "info-item",
229                strong { "⏰ Lamport Timestamp: " }
230                span { "{lamport}" }
231            }
232            div { class: "info-item",
233                strong { "⏱️ Vector Clock : " }
234                span { "{vector_clock}" }
235            }
236            div { class: "info-item",
237                strong { "🌍 Number of connected neighbours: " }
238                span { "{nb_neighbours}" }
239            }
240
241            div { class: "info-item",
242                strong { "🤝 Connected Neighbours: " }
243                if connected_neighbours.read().is_empty() {
244                    span { "No peers currently connected." }
245                } else {
246                    ul { class: "peer-list",
247                        for adr in connected_neighbours.read().iter() {
248                            li { key: "{adr}", "{adr}" }
249                        }
250                    }
251                }
252            }
253
254            div { class: "info-item",
255                strong { "🌍 Number of CLI peers: " }
256                span { "{nb_peers}" }
257            }
258
259            div { class: "info-item",
260                strong { "🤝 CLI peers adresses: " }
261                if peers_addr.read().is_empty() {
262                    span { "No peers currently connected." }
263                } else {
264                    ul { class: "peer-list",
265                        for adr in peers_addr.read().iter() {
266                            li { key: "{adr}", "{adr}" }
267                        }
268                    }
269                }
270            }
271
272            div {
273                class: "info-item",
274                style: "display: flex; justify-content: center;",
275                form {
276                    button {
277                        class: "snapshot",
278                        r#type: "submit",
279                        onclick: move |_| {
280                            async move {
281                                if let Err(e) = ask_for_snapshot().await {
282                                    log::error!("Error taking snapshot: {e}");
283                                }
284                            }
285                        },
286                        "Take a snapshot"
287                    }
288                }
289            }
290
291            div { class: "info-item",
292                strong { "📄 Last Snapshot Content:" }
293                if let Some(content) = snapshot_content.read().as_ref() {
294                    pre { "{content}" }
295                } else {
296                    span { "No snapshot available." }
297                }
298            }
299        }
300    }
301}