use maud::{html, Markup, PreEscaped}; use peach_network::network; use vnstat_parse::Vnstat; use crate::{templates, utils::theme, AP_IFACE, WLAN_IFACE}; enum NetworkState { AccessPoint, WiFiClient, } // ROUTE: /status/network /// 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", ), }, }; 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) }