From 4e7fbd5fdf9ead55742a6b9818ab7ffe8091208c Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 25 Oct 2022 15:14:52 +0100 Subject: [PATCH] add the refactored template for network status --- peach-web/src/routes/status/mod.rs | 2 +- peach-web/src/routes/status/network.rs | 292 +++++++++++++++++++++++-- 2 files changed, 279 insertions(+), 15 deletions(-) diff --git a/peach-web/src/routes/status/mod.rs b/peach-web/src/routes/status/mod.rs index 90d533e..d68980b 100644 --- a/peach-web/src/routes/status/mod.rs +++ b/peach-web/src/routes/status/mod.rs @@ -1,3 +1,3 @@ //pub mod device; -//pub mod network; +pub mod network; pub mod scuttlebutt; diff --git a/peach-web/src/routes/status/network.rs b/peach-web/src/routes/status/network.rs index c75c0dc..d3ca1bc 100644 --- a/peach-web/src/routes/status/network.rs +++ b/peach-web/src/routes/status/network.rs @@ -1,21 +1,285 @@ -use rocket::{get, request::FlashMessage}; -use rocket_dyn_templates::Template; +use maud::{html, Markup, PreEscaped}; +use peach_network::network; +use vnstat_parse::Vnstat; -use crate::context::network::NetworkStatusContext; -use crate::routes::authentication::Authenticated; +use crate::{templates, utils::theme, AP_IFACE, WLAN_IFACE}; -// HELPERS AND ROUTES FOR /status/network +enum NetworkState { + AccessPoint, + WiFiClient, +} -#[get("/network")] -pub fn network_status(flash: Option, _auth: Authenticated) -> Template { - let mut context = NetworkStatusContext::build(); - context.back = Some("/status".to_string()); - context.title = Some("Network Status".to_string()); +// ROUTE: /status/network - if let Some(flash) = flash { - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); +/// Render the cog icon which is used as a link to the network settings page. +fn render_network_config_icon() -> Markup { + html! { + (PreEscaped("")) + a class="link two-grid-top-right" href="/settings/network" title="Configure network settings" { + img id="configureNetworking" class="icon-small" src="/icons/cog.svg" alt="Configure"; + } + } +} + +/// Render the network mode icon, either a WiFi signal or router, based +/// on the state of the AP and WiFi interfaces. +/// +/// A router icon is shown if the AP is online (interface is "up"). +/// +/// A WiFi signal icon is shown if the AP interface is down. The colour of +/// the icon is black if the WLAN interface is up and gray if it's down. +fn render_network_mode_icon(state: &NetworkState) -> Markup { + // TODO: make this DRYer + let (icon_class, icon_src, icon_alt, label_title, label_value) = match state { + NetworkState::AccessPoint => ( + "center icon icon-active", + "/icons/router.svg", + "WiFi router", + "Access Point Online", + "ONLINE", + ), + NetworkState::WiFiClient => match network::state(WLAN_IFACE) { + Ok(Some(state)) if state == "up" => ( + "center icon icon-active", + "/icons/wifi.svg", + "WiFi signal", + "WiFi Client Online", + "ONLINE", + ), + _ => ( + "center icon icon-inactive", + "/icons/wifi.svg", + "WiFi signal", + "WiFi Client Offline", + "OFFLINE", + ), + }, }; - Template::render("status/network", &context) + html! { + (PreEscaped("")) + div class="grid-column-1" { + img id="netModeIcon" class=(icon_class) src=(icon_src) alt=(icon_alt); + label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" title=(label_title) { (label_value) } + } + } +} + +/// Render the network data associated with the deployed access point or +/// connected WiFi client depending on active mode. +/// +/// Data includes the network mode (access point or WiFi client), SSID and IP +/// address. +fn render_network_data(state: &NetworkState, ssid: String, ip: String) -> Markup { + let (mode_value, mode_title, ssid_value, ip_title) = match state { + NetworkState::AccessPoint => ( + "Access Point", + "Access Point SSID", + // TODO: remove hardcoding of this value (query interface instead) + "peach", + "Access Point IP Address", + ), + NetworkState::WiFiClient => ( + "WiFi Client", + "WiFi SSID", + ssid.as_str(), + "WiFi Client IP Address", + ), + }; + + html! { + (PreEscaped("")) + div class="grid-column-2" { + label class="label-small font-gray" for="netMode" title="Network Mode" { "MODE" } + p id="netMode" class="card-text" title="Network Mode" { (mode_value) } + label class="label-small font-gray" for="netSsid" title=(mode_title) { "SSID" } + p id="netSsid" class="card-text" title="SSID" { (ssid_value) } + label class="label-small font-gray" for="netIp" title=(ip_title) { "IP" } + p id="netIp" class="card-text" title="IP" { (ip) } + } + } +} + +/// Render the network status grid comprised of the network config icon, +/// network mode icon and network data text. +fn render_network_status_grid(state: &NetworkState, ssid: String, ip: String) -> Markup { + html! { + (PreEscaped("")) + div class="two-grid" title="PeachCloud network mode and status" { + (render_network_config_icon()) + (PreEscaped("")) + (render_network_mode_icon(state)) + (PreEscaped("")) + (render_network_data(state, ssid, ip)) + } + } +} + +/// Render the signal strength stack comprised of a signal icon, RSSI value +/// and label. +/// +/// This stack is displayed when the network mode is set to WiFi +/// client (ie. the value reported is the strength of the connection of the +/// local WiFi interface to a remote access point). +fn render_signal_strength_stack() -> Markup { + let wlan_rssi = match network::rssi(WLAN_IFACE) { + Ok(Some(rssi)) => rssi, + _ => 0.to_string(), + }; + + html! { + div class="stack" { + img id="netSignal" class="icon icon-medium" alt="Signal" title="WiFi Signal (%)" src="/icons/low-signal.svg"; + div class="flex-grid" style="padding-top: 0.5rem;" { + label class="label-medium" for="netSignal" style="padding-right: 3px;" title="Signal strength of WiFi connection (%)" { (wlan_rssi) } + } + label class="label-small font-gray" { "SIGNAL" } + } + } +} + +/// Render the connected devices stack comprised of a devices icon, value +/// of connected devices and label. +/// +/// This stack is displayed when the network mode is set to access point +/// (ie. the value reported is the number of remote devices connected to the +/// local access point). +fn render_connected_devices_stack() -> Markup { + html! { + div class="stack" { + img id="devices" class="icon icon-medium" title="Connected devices" src="/icons/devices.svg" alt="Digital devices"; + div class="flex-grid" style="padding-top: 0.5rem;" { + label class="label-medium" for="devices" style="padding-right: 3px;" title="Number of connected devices"; + } + label class="label-small font-gray" { "DEVICES" } + } + } +} + +/// Render the data download stack comprised of a download icon, traffic value +/// and label. +/// +/// A zero value is displayed if no interface traffic is available for the +/// WLAN interface. +fn render_data_download_stack(iface_traffic: &Option) -> Markup { + html! { + div class="stack" { + img id="dataDownload" class="icon icon-medium" title="Download" src="/icons/down-arrow.svg" alt="Download"; + div class="flex-grid" style="padding-top: 0.5rem;" { + @if let Some(traffic) = iface_traffic { + label class="label-medium" for="dataDownload" style="padding-right: 3px;" title={ "Data download total in " (traffic.all_time_rx_unit) } { (traffic.all_time_rx) } + label class="label-small font-near-black" { (traffic.all_time_rx_unit) } + } @else { + label class="label-medium" for="dataDownload" style="padding-right: 3px;" title="Data download total" { "0" } + label class="label-small font-near-black"; + } + } + label class="label-small font-gray" { "DOWNLOAD" } + } + } +} + +/// Render the data upload stack comprised of an upload icon, traffic value +/// and label. +/// +/// A zero value is displayed if no interface traffic is available for the +/// WLAN interface. +fn render_data_upload_stack(iface_traffic: Option) -> Markup { + html! { + div class="stack" { + img id="dataUpload" class="icon icon-medium" title="Upload" src="/icons/up-arrow.svg" alt="Upload"; + div class="flex-grid" style="padding-top: 0.5rem;" { + @if let Some(traffic) = iface_traffic { + label class="label-medium" for="dataUpload" style="padding-right: 3px;" title={ "Data upload total in " (traffic.all_time_tx_unit) } { (traffic.all_time_tx) } + label class="label-small font-near-black" { (traffic.all_time_tx_unit) } + } @else { + label class="label-medium" for="dataUpload" style="padding-right: 3px;" title="Data upload total" { "0" } + label class="label-small font-near-black"; + } + } + label class="label-small font-gray" { "UPLOAD" } + } + } +} + +/// Render the device / signal and traffic grid. +/// +/// The connected devices stack is displayed if the network mode is set to +/// access point and the signal strength stack is displayed if the network +/// mode is set to WiFi client. +fn render_device_and_traffic_grid(state: NetworkState, iface_traffic: Option) -> Markup { + html! { + div class="three-grid card-container" { + @match state { + NetworkState::AccessPoint => (render_connected_devices_stack()), + NetworkState::WiFiClient => (render_signal_strength_stack()), + } + (render_data_download_stack(&iface_traffic)) + (render_data_upload_stack(iface_traffic)) + } + } +} + +/// Network state data retrieval. +/// +/// This data is injected into the template rendering functions. +fn retrieve_network_data() -> (NetworkState, Option, String, String) { + // if the access point interface is "up", + // retrieve the traffic stats, ip and ssidfor the ap interface. + // otherwise retrieve the stats and ip for the wlan interface. + let (state, traffic, ip, ssid) = match network::state(AP_IFACE) { + Ok(Some(state)) if state == "up" => { + let ap_traffic = Vnstat::get(AP_IFACE).ok(); + + let ap_ip = match network::ip(AP_IFACE) { + Ok(Some(ip)) => ip, + _ => String::from("x.x.x.x"), + }; + + let ap_ssid = String::from("peach"); + + (NetworkState::AccessPoint, ap_traffic, ap_ip, ap_ssid) + } + _ => { + let wlan_traffic = Vnstat::get(WLAN_IFACE).ok(); + + let wlan_ip = match network::ip(WLAN_IFACE) { + Ok(Some(ip)) => ip, + _ => String::from("x.x.x.x"), + }; + + let wlan_ssid = match network::ssid(WLAN_IFACE) { + Ok(Some(ssid)) => ssid, + _ => String::from("Not connected"), + }; + + (NetworkState::WiFiClient, wlan_traffic, wlan_ip, wlan_ssid) + } + }; + + (state, traffic, ip, ssid) +} + +/// Network status template builder. +pub fn build_template() -> PreEscaped { + let (state, traffic, ip, ssid) = retrieve_network_data(); + + let network_status_template = html! { + (PreEscaped("")) + div class="card center" { + (PreEscaped("")) + div class="capsule capsule-container success-border" { + (render_network_status_grid(&state, ssid, ip)) + hr style="color: var(--light-gray);"; + (render_device_and_traffic_grid(state, traffic)) + } + } + }; + + let body = + templates::nav::build_template(network_status_template, "Network Status", Some("/status")); + + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) }