2022-10-25 14:14:52 +00:00
use maud ::{ html , Markup , PreEscaped } ;
use peach_network ::network ;
use vnstat_parse ::Vnstat ;
2021-11-16 13:50:06 +00:00
2022-10-25 14:14:52 +00:00
use crate ::{ templates , utils ::theme , AP_IFACE , WLAN_IFACE } ;
2021-11-16 13:50:06 +00:00
2022-10-25 14:14:52 +00:00
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 ( " <!-- top-right config icon --> " ) )
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 ( " <!-- network mode icon with label --> " ) )
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 ( " <!-- network mode, ssid & ip with labels --> " ) )
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 ( " <!-- NETWORK STATUS GRID --> " ) )
div class = " two-grid " title = " PeachCloud network mode and status " {
( render_network_config_icon ( ) )
( PreEscaped ( " <!-- left column --> " ) )
( render_network_mode_icon ( state ) )
( PreEscaped ( " <!-- right column --> " ) )
( 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 < Vnstat > ) -> 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 < Vnstat > ) -> 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 < Vnstat > ) -> 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 < Vnstat > , 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 " ) ;
2021-11-16 13:50:06 +00:00
2022-10-25 14:14:52 +00:00
( NetworkState ::AccessPoint , ap_traffic , ap_ip , ap_ssid )
}
_ = > {
let wlan_traffic = Vnstat ::get ( WLAN_IFACE ) . ok ( ) ;
2022-01-13 13:49:12 +00:00
2022-10-25 14:14:52 +00:00
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 )
}
2021-11-16 13:50:06 +00:00
} ;
2022-01-13 13:49:12 +00:00
2022-10-25 14:14:52 +00:00
( state , traffic , ip , ssid )
}
/// Network status template builder.
pub fn build_template ( ) -> PreEscaped < String > {
let ( state , traffic , ip , ssid ) = retrieve_network_data ( ) ;
let network_status_template = html! {
( PreEscaped ( " <!-- NETWORK STATUS CARD --> " ) )
div class = " card center " {
( PreEscaped ( " <!-- NETWORK INFO BOX --> " ) )
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 )
2021-11-16 13:50:06 +00:00
}