From 72b728158788b58b4958477875723573548eac91 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 12 Jan 2022 19:51:08 +0200 Subject: [PATCH 01/18] remove json api tests --- peach-web/src/tests.rs | 176 ----------------------------------------- 1 file changed, 176 deletions(-) diff --git a/peach-web/src/tests.rs b/peach-web/src/tests.rs index e95c794..d679861 100644 --- a/peach-web/src/tests.rs +++ b/peach-web/src/tests.rs @@ -3,11 +3,8 @@ use std::io::Read; use rocket::http::{ContentType, Status}; use rocket::local::blocking::Client; -use rocket::serde::json::{json, Value}; use rocket::{Build, Config, Rocket}; -use crate::utils::build_json_response; - use super::init_rocket; // define authentication mode @@ -509,179 +506,6 @@ fn network_status_html() { assert!(body.contains("DOWNLOAD")); assert!(body.contains("UPLOAD")); } - -// JSON API ROUTES - -#[test] -fn activate_ap() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/api/v1/network/activate_ap") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); -} - -#[test] -fn activate_client() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/api/v1/network/activate_client") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); -} - -#[test] -fn return_ip() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/api/v1/network/ip") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("wlan0")); - assert!(body.contains("ap0")); -} - -#[test] -fn return_rssi() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/api/v1/network/rssi") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("Not currently connected to an access point.")); -} - -#[test] -fn return_ssid() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/api/v1/network/ssid") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("Not currently connected to an access point.")); -} - -#[test] -fn return_state() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/api/v1/network/state") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("ap0")); - assert!(body.contains("wlan0")); - assert!(body.contains("unavailable")); -} - -#[test] -fn return_status() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/api/v1/network/status") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("Not currently connected to an access point.")); -} - -#[test] -fn scan_networks() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/api/v1/network/wifi") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("Unable to scan for networks. Interface may be deactivated.")); -} - -#[test] -fn add_wifi() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/api/v1/network/wifi") - .header(ContentType::JSON) - .body(r#"{ "ssid": "Home", "pass": "Password" }"#) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("Failed to add WiFi credentials.")); -} - -#[test] -fn remove_wifi() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/api/v1/network/wifi/forget") - .header(ContentType::JSON) - .body(r#"{ "ssid": "Home" }"#) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("Failed to remove WiFi network credentials.")); -} - -#[test] -fn new_password() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/api/v1/network/wifi/modify") - .header(ContentType::JSON) - .body(r#"{ "ssid": "Home", "pass": "Password" }"#) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("Failed to update WiFi password.")); -} - -#[test] -fn ping_pong() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/api/v1/ping") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - let body = response.into_string().unwrap(); - assert!(body.contains("pong!")); -} - -// HELPER FUNCTION TESTS - -#[test] -fn test_build_json_response() { - let status = "success".to_string(); - let data = json!("WiFi credentials added.".to_string()); - let j: Value = build_json_response(status, Some(data), None); - assert_eq!(j["status"], "success"); - assert_eq!(j["data"], "WiFi credentials added."); - assert_eq!(j["msg"], json!(null)); -} - // FILE TESTS #[test] From bb5cd0f0d32179b8091cf48f5db16b2721992f0e Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 12 Jan 2022 19:54:30 +0200 Subject: [PATCH 02/18] remove unneeded dependencies --- peach-web/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index e7c9a7f..f0b6d69 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -39,12 +39,9 @@ maintenance = { status = "actively-developed" } env_logger = "0.8" log = "0.4" nest = "1.0.0" -openssl = { version = "0.10", features = ["vendored"] } peach-lib = { path = "../peach-lib" } peach-network = { path = "../peach-network", features = ["serde_support"] } peach-stats = { path = "../peach-stats", features = ["serde_support"] } -percent-encoding = "2.1.0" -regex = "1" rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" From fd94ba27ac86142352a92884db06860c54883a5a Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 12 Jan 2022 19:58:49 +0200 Subject: [PATCH 03/18] replace snafu with custom error impl --- peach-web/Cargo.toml | 1 - peach-web/src/error.rs | 56 +++++++++++++++++++--------- peach-web/src/routes/settings/dns.rs | 2 +- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index f0b6d69..fbaced9 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -45,7 +45,6 @@ peach-stats = { path = "../peach-stats", features = ["serde_support"] } rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -snafu = "0.6" tera = { version = "1.12.1", features = ["builtins"] } xdg = "2.2.0" diff --git a/peach-web/src/error.rs b/peach-web/src/error.rs index a24d253..6288b6b 100644 --- a/peach-web/src/error.rs +++ b/peach-web/src/error.rs @@ -2,35 +2,57 @@ use peach_lib::error::PeachError; use peach_lib::{serde_json, serde_yaml}; -use snafu::Snafu; +use serde_json::error::Error as JsonError; +use serde_yaml::Error as YamlError; -#[derive(Debug, Snafu)] +/// Custom error type encapsulating all possible errors for the web application. +#[derive(Debug)] pub enum PeachWebError { - #[snafu(display("Error loading serde json"))] - Serde { source: serde_json::error::Error }, - #[snafu(display("Error loading peach-config yaml"))] - YamlError { source: serde_yaml::Error }, - #[snafu(display("{}", msg))] - FailedToRegisterDynDomain { msg: String }, - #[snafu(display("{}: {}", source, msg))] - PeachLibError { source: PeachError, msg: String }, + Json(JsonError), + Yaml(YamlError), + FailedToRegisterDynDomain(String), + PeachLib { source: PeachError, msg: String }, } -impl From for PeachWebError { - fn from(err: serde_json::error::Error) -> PeachWebError { - PeachWebError::Serde { source: err } +impl std::error::Error for PeachWebError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + PeachWebError::Json(ref source) => Some(source), + PeachWebError::Yaml(ref source) => Some(source), + PeachWebError::FailedToRegisterDynDomain(_) => None, + PeachWebError::PeachLib { ref source, .. } => Some(source), + } } } -impl From for PeachWebError { - fn from(err: serde_yaml::Error) -> PeachWebError { - PeachWebError::YamlError { source: err } +impl std::fmt::Display for PeachWebError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), + PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source), + PeachWebError::FailedToRegisterDynDomain(ref msg) => { + write!(f, "DYN DNS error: {}", msg) + } + PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source), + } + } +} + +impl From for PeachWebError { + fn from(err: JsonError) -> PeachWebError { + PeachWebError::Json(err) + } +} + +impl From for PeachWebError { + fn from(err: YamlError) -> PeachWebError { + PeachWebError::Yaml(err) } } impl From for PeachWebError { fn from(err: PeachError) -> PeachWebError { - PeachWebError::PeachLibError { + PeachWebError::PeachLib { source: err, msg: "".to_string(), } diff --git a/peach-web/src/routes/settings/dns.rs b/peach-web/src/routes/settings/dns.rs index d9bf10a..7abfd11 100644 --- a/peach-web/src/routes/settings/dns.rs +++ b/peach-web/src/routes/settings/dns.rs @@ -65,7 +65,7 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> { } _ => "Failed to register dyndns domain".to_string(), }; - Err(PeachWebError::FailedToRegisterDynDomain { msg }) + Err(PeachWebError::FailedToRegisterDynDomain(msg)) } } } From cfd50ca359d829e9bdaddeb1b3a041efa1fcfc5a Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 12 Jan 2022 20:21:05 +0200 Subject: [PATCH 04/18] cleanup paths and add whitespace --- peach-web/src/routes/settings/dns.rs | 58 +++++++++++++++------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/peach-web/src/routes/settings/dns.rs b/peach-web/src/routes/settings/dns.rs index 7abfd11..9e1556e 100644 --- a/peach-web/src/routes/settings/dns.rs +++ b/peach-web/src/routes/settings/dns.rs @@ -7,16 +7,12 @@ use rocket::{ }; use rocket_dyn_templates::Template; -use peach_lib::config_manager; -use peach_lib::config_manager::load_peach_config; -use peach_lib::dyndns_client; -use peach_lib::dyndns_client::{ - check_is_new_dyndns_domain, get_dyndns_subdomain, get_full_dynamic_domain, - is_dns_updater_online, +use peach_lib::{ + config_manager, dyndns_client, + error::PeachError, + jsonrpc_client_core::{Error, ErrorKind}, + jsonrpc_core::types::error::ErrorCode, }; -use peach_lib::error::PeachError; -use peach_lib::jsonrpc_client_core::{Error, ErrorKind}; -use peach_lib::jsonrpc_core::types::error::ErrorCode; use crate::error::PeachWebError; use crate::routes::authentication::Authenticated; @@ -32,11 +28,12 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> { // first save local configurations config_manager::set_external_domain(&dns_form.external_domain)?; config_manager::set_dyndns_enabled_value(dns_form.enable_dyndns)?; + // if dynamic dns is enabled and this is a new domain name, then register it if dns_form.enable_dyndns { - let full_dynamic_domain = get_full_dynamic_domain(&dns_form.dynamic_domain); + let full_dynamic_domain = dyndns_client::get_full_dynamic_domain(&dns_form.dynamic_domain); // check if this is a new domain or if its already registered - let is_new_domain = check_is_new_dyndns_domain(&full_dynamic_domain)?; + let is_new_domain = dyndns_client::check_is_new_dyndns_domain(&full_dynamic_domain)?; if is_new_domain { match dyndns_client::register_domain(&full_dynamic_domain) { Ok(_) => { @@ -48,19 +45,17 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> { info!("Failed to register dyndns domain: {:?}", err); // json response for failed update let msg: String = match err { - PeachError::JsonRpcClientCore(source) => { - match source { - Error(ErrorKind::JsonRpcError(err), _state) => match err.code { - ErrorCode::ServerError(-32030) => { - format!("Error registering domain: {} was previously registered", full_dynamic_domain) - } - _ => { - format!("Failed to register dyndns domain {:?}", err) - } - }, - _ => { - format!("Failed to register dyndns domain: {:?}", source) - } + PeachError::JsonRpcClientCore(Error( + ErrorKind::JsonRpcError(err), + _state, + )) => { + if let ErrorCode::ServerError(-32030) = err.code { + format!( + "Error registering domain: {} was previously registered", + full_dynamic_domain + ) + } else { + "Failed to register dyndns domain".to_string() } } _ => "Failed to register dyndns domain".to_string(), @@ -92,11 +87,13 @@ pub struct ConfigureDNSContext { impl ConfigureDNSContext { pub fn build() -> ConfigureDNSContext { - let peach_config = load_peach_config().unwrap(); + // TODO: replace `unwrap` with resilient error handling + let peach_config = config_manager::load_peach_config().unwrap(); let dyndns_fulldomain = peach_config.dyn_domain; - let is_dyndns_online = is_dns_updater_online().unwrap(); + let is_dyndns_online = dyndns_client::is_dns_updater_online().unwrap(); let dyndns_subdomain = - get_dyndns_subdomain(&dyndns_fulldomain).unwrap_or(dyndns_fulldomain); + dyndns_client::get_dyndns_subdomain(&dyndns_fulldomain).unwrap_or(dyndns_fulldomain); + ConfigureDNSContext { external_domain: peach_config.external_domain, dyndns_subdomain, @@ -113,15 +110,18 @@ impl ConfigureDNSContext { #[get("/dns")] pub fn configure_dns(flash: Option, _auth: Authenticated) -> Template { let mut context = ConfigureDNSContext::build(); + // set back icon link to network route context.back = Some("/settings/network".to_string()); context.title = Some("Configure DNS".to_string()); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; + Template::render("settings/network/configure_dns", &context) } @@ -131,20 +131,24 @@ pub fn configure_dns_post(dns: Form, _auth: Authenticated) -> Template match result { Ok(_) => { let mut context = ConfigureDNSContext::build(); + // set back icon link to network route context.back = Some("/settings/network".to_string()); context.title = Some("Configure DNS".to_string()); context.flash_name = Some("success".to_string()); context.flash_msg = Some("New dynamic dns configuration is now enabled".to_string()); + Template::render("settings/network/configure_dns", &context) } Err(err) => { let mut context = ConfigureDNSContext::build(); + // set back icon link to network route context.back = Some("/settings/network".to_string()); context.title = Some("Configure DNS".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some(format!("Failed to save dns configurations: {}", err)); + Template::render("settings/network/configure_dns", &context) } } From 08ee9cd776e567cba234fcf960767040f406a644 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 12 Jan 2022 20:21:39 +0200 Subject: [PATCH 05/18] cargo fmt --- peach-web/src/utils/monitor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/utils/monitor.rs b/peach-web/src/utils/monitor.rs index 77edcfd..fd896cf 100644 --- a/peach-web/src/utils/monitor.rs +++ b/peach-web/src/utils/monitor.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use nest::{Error, Store, Value}; -use rocket::form::{FromForm}; +use rocket::form::FromForm; use rocket::serde::{Deserialize, Serialize}; use serde_json::json; From b0b21ad8a012d5f5fd37949a056ac7af59ed0897 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 13 Jan 2022 13:15:42 +0200 Subject: [PATCH 06/18] add standalone check before mounting routes --- peach-web/Cargo.toml | 1 + peach-web/src/main.rs | 113 ++++++-------------------------- peach-web/src/router.rs | 142 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 92 deletions(-) create mode 100644 peach-web/src/router.rs diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index fbaced9..409c82c 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -37,6 +37,7 @@ maintenance = { status = "actively-developed" } [dependencies] env_logger = "0.8" +lazy_static = "1.4.0" log = "0.4" nest = "1.0.0" peach-lib = { path = "../peach-lib" } diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 481f7a6..92528d6 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -25,106 +25,36 @@ #![feature(proc_macro_hygiene, decl_macro)] pub mod error; +mod router; pub mod routes; #[cfg(test)] mod tests; pub mod utils; +use std::{env, process}; + +use lazy_static::lazy_static; use log::{error, info}; -use std::process; - -use rocket::{catchers, fs::FileServer, routes, Build, Rocket}; -use rocket_dyn_templates::Template; - -use crate::routes::authentication::*; -use crate::routes::catchers::*; -use crate::routes::index::*; -use crate::routes::scuttlebutt::*; -use crate::routes::status::device::*; -use crate::routes::status::network::*; - -use crate::routes::settings::admin::*; -use crate::routes::settings::dns::*; -use crate::routes::settings::menu::*; -use crate::routes::settings::network::*; -use crate::routes::settings::scuttlebutt::*; +use rocket::{Build, Rocket}; pub type BoxError = Box; -/// Create rocket instance & mount all routes. -fn init_rocket() -> Rocket { - rocket::build() - // GENERAL HTML ROUTES - .mount( - "/", - routes![ - help, - home, - login, - login_post, - logout, - reboot_cmd, - shutdown_cmd, - power_menu, - settings_menu, - ], - ) - // STATUS HTML ROUTES - .mount("/status", routes![device_status, network_status]) - // ADMIN SETTINGS HTML ROUTES - .mount( - "/settings/admin", - routes![ - admin_menu, - configure_admin, - add_admin, - add_admin_post, - delete_admin_post, - change_password, - change_password_post, - reset_password, - reset_password_post, - forgot_password_page, - send_password_reset_post, - ], - ) - // NETWORK SETTINGS HTML ROUTES - .mount( - "/settings/network", - routes![ - add_credentials, - connect_wifi, - configure_dns, - configure_dns_post, - disconnect_wifi, - deploy_ap, - deploy_client, - forget_wifi, - network_home, - add_ssid, - add_wifi, - network_detail, - wifi_list, - wifi_password, - wifi_set_password, - wifi_usage, - wifi_usage_alerts, - wifi_usage_reset, - ], - ) - // SCUTTLEBUTT SETTINGS HTML ROUTES - .mount("/settings/scuttlebutt", routes![ssb_settings_menu]) - // SCUTTLEBUTT SOCIAL HTML ROUTES - .mount( - "/scuttlebutt", - routes![ - peers, friends, follows, followers, blocks, profile, private, follow, unfollow, - block, publish, - ], - ) - .mount("/", FileServer::from("static")) - .register("/", catchers![not_found, internal_error, forbidden]) - .attach(Template::fairing()) +lazy_static! { + // determine run-mode from env var; default to standalone mode (aka peachpub) + static ref STANDALONE_MODE: bool = match env::var("PEACH_STANDALONE_MODE") { + // parse the value to a boolean; default to true for any error + Ok(val) => val.parse().unwrap_or(true), + Err(_) => true + }; +} + +pub fn init_rocket() -> Rocket { + info!("Initializing Rocket"); + if *STANDALONE_MODE { + router::build_minimal_rocket() + } else { + router::build_complete_rocket() + } } /// Launch the peach-web rocket server. @@ -134,7 +64,6 @@ async fn main() { env_logger::init(); // initialize rocket - info!("Initializing Rocket"); let rocket = init_rocket(); // launch rocket diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs new file mode 100644 index 0000000..f86148b --- /dev/null +++ b/peach-web/src/router.rs @@ -0,0 +1,142 @@ +use rocket::{catchers, fs::FileServer, routes, Build, Rocket}; +use rocket_dyn_templates::Template; + +use crate::routes::{ + authentication::*, + catchers::*, + index::*, + scuttlebutt::*, + settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*}, + status::{device::*, network::*}, +}; + +/// Create minimal rocket instance and mount routes. This excludes settings +/// and status routes related to networking and the device (memory, +/// hard disk, CPU etc.). +pub fn build_minimal_rocket() -> Rocket { + rocket::build() + // GENERAL HTML ROUTES + .mount( + "/", + routes![ + help, + home, + login, + login_post, + logout, + reboot_cmd, + shutdown_cmd, + power_menu, + settings_menu, + ], + ) + // ADMIN SETTINGS HTML ROUTES + .mount( + "/settings/admin", + routes![ + admin_menu, + configure_admin, + add_admin, + add_admin_post, + delete_admin_post, + change_password, + change_password_post, + reset_password, + reset_password_post, + forgot_password_page, + send_password_reset_post, + ], + ) + // SCUTTLEBUTT SETTINGS HTML ROUTES + .mount("/settings/scuttlebutt", routes![ssb_settings_menu]) + // SCUTTLEBUTT SOCIAL HTML ROUTES + .mount( + "/scuttlebutt", + routes![ + peers, friends, follows, followers, blocks, profile, private, follow, unfollow, + block, publish, + ], + ) + // STATUS HTML ROUTES + // TODO: replace this with a route for `scuttlebutt_status` + .mount("/status", routes![device_status, network_status]) + .mount("/", FileServer::from("static")) + .register("/", catchers![not_found, internal_error, forbidden]) + .attach(Template::fairing()) +} + +/// Create complete rocket instance and mount all routes. +pub fn build_complete_rocket() -> Rocket { + rocket::build() + // GENERAL HTML ROUTES + .mount( + "/", + routes![ + help, + home, + login, + login_post, + logout, + reboot_cmd, + shutdown_cmd, + power_menu, + settings_menu, + ], + ) + // ADMIN SETTINGS HTML ROUTES + .mount( + "/settings/admin", + routes![ + admin_menu, + configure_admin, + add_admin, + add_admin_post, + delete_admin_post, + change_password, + change_password_post, + reset_password, + reset_password_post, + forgot_password_page, + send_password_reset_post, + ], + ) + // NETWORK SETTINGS HTML ROUTES + .mount( + "/settings/network", + routes![ + add_credentials, + connect_wifi, + configure_dns, + configure_dns_post, + disconnect_wifi, + deploy_ap, + deploy_client, + forget_wifi, + network_home, + add_ssid, + add_wifi, + network_detail, + wifi_list, + wifi_password, + wifi_set_password, + wifi_usage, + wifi_usage_alerts, + wifi_usage_reset, + ], + ) + // SCUTTLEBUTT SETTINGS HTML ROUTES + .mount("/settings/scuttlebutt", routes![ssb_settings_menu]) + // SCUTTLEBUTT SOCIAL HTML ROUTES + .mount( + "/scuttlebutt", + routes![ + peers, friends, follows, followers, blocks, profile, private, follow, unfollow, + block, publish, + ], + ) + // STATUS HTML ROUTES + .mount("/status", routes![device_status, network_status]) + .mount("/", FileServer::from("static")) + .register("/", catchers![not_found, internal_error, forbidden]) + .attach(Template::fairing()) +} From f4ad230d5813ac4da24bd8b369ec9f3d267c9523 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 13 Jan 2022 13:16:38 +0200 Subject: [PATCH 07/18] remove unnecessary context objects --- peach-web/src/routes/authentication.rs | 295 +++++++++-------------- peach-web/src/routes/index.rs | 66 ++--- peach-web/src/routes/settings/admin.rs | 130 ++++------ peach-web/src/routes/settings/menu.rs | 39 +-- peach-web/src/routes/settings/network.rs | 14 +- 5 files changed, 186 insertions(+), 358 deletions(-) diff --git a/peach-web/src/routes/authentication.rs b/peach-web/src/routes/authentication.rs index 6a77a45..97ea167 100644 --- a/peach-web/src/routes/authentication.rs +++ b/peach-web/src/routes/authentication.rs @@ -1,14 +1,17 @@ use log::info; -use rocket::form::{Form, FromForm}; -use rocket::http::{Cookie, CookieJar, Status}; -use rocket::request::{self, FlashMessage, FromRequest, Request}; -use rocket::response::{Flash, Redirect}; -use rocket::serde::{Deserialize, Serialize}; -use rocket::{get, post, Config}; -use rocket_dyn_templates::Template; +use rocket::{ + form::{Form, FromForm}, + get, + http::{Cookie, CookieJar, Status}, + post, + request::{self, FlashMessage, FromRequest, Request}, + response::{Flash, Redirect}, + serde::Deserialize, + Config, +}; +use rocket_dyn_templates::{tera::Context, Template}; -use peach_lib::error::PeachError; -use peach_lib::password_utils; +use peach_lib::{error::PeachError, password_utils}; use crate::error::PeachWebError; use crate::utils::TemplateOrRedirect; @@ -66,37 +69,19 @@ impl<'r> FromRequest<'r> for Authenticated { // HELPERS AND ROUTES FOR /login -#[derive(Debug, Serialize)] -pub struct LoginContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, -} - -impl LoginContext { - pub fn build() -> LoginContext { - LoginContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - } - } -} - #[get("/login")] pub fn login(flash: Option) -> Template { - let mut context = LoginContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Login".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Login".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("login", &context) + + Template::render("login", &context.into_json()) } #[derive(Debug, Deserialize, FromForm)] @@ -116,8 +101,7 @@ pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { #[post("/login", data = "")] pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> TemplateOrRedirect { - let result = verify_login_form(login_form.into_inner()); - match result { + match verify_login_form(login_form.into_inner()) { Ok(_) => { // if successful login, add a cookie indicating the user is authenticated // and redirect to home page @@ -125,17 +109,18 @@ pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> Templ // is just admin (this is arbitrary). // If we had multiple users, we could put the user_id here. cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME)); + TemplateOrRedirect::Redirect(Redirect::to("/")) } Err(_) => { // if unsuccessful login, render /login page again - let mut context = LoginContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Login".to_string()); - context.flash_name = Some("error".to_string()); - let flash_msg = "Invalid password".to_string(); - context.flash_msg = Some(flash_msg); - TemplateOrRedirect::Template(Template::render("login", &context)) + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Login".to_string())); + context.insert("flash_name", &("error".to_string())); + context.insert("flash_msg", &("Invalid password".to_string())); + + TemplateOrRedirect::Template(Template::render("login", &context.into_json())) } } } @@ -159,44 +144,6 @@ pub struct ResetPasswordForm { pub new_password2: String, } -#[derive(Debug, Serialize)] -pub struct ResetPasswordContext { - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl ResetPasswordContext { - pub fn build() -> ResetPasswordContext { - ResetPasswordContext { - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - -#[derive(Debug, Serialize)] -pub struct ChangePasswordContext { - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl ChangePasswordContext { - pub fn build() -> ChangePasswordContext { - ChangePasswordContext { - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - /// Verify, validate and save the submitted password. This function is publicly exposed for users who have forgotten their password. pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), PeachWebError> { info!( @@ -218,81 +165,63 @@ pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), /// and is specifically for users who have forgotten their password. #[get("/reset_password")] pub fn reset_password(flash: Option) -> Template { - let mut context = ResetPasswordContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Reset Password".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Reset Password".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("settings/admin/reset_password", &context) + + Template::render("settings/admin/reset_password", &context.into_json()) } /// Password reset form request handler. This route is used by a user who is not logged in /// and is specifically for users who have forgotten their password. #[post("/reset_password", data = "")] pub fn reset_password_post(reset_password_form: Form) -> Template { - let result = save_reset_password_form(reset_password_form.into_inner()); - match result { - Ok(_) => { - let mut context = ChangePasswordContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Reset Password".to_string()); - context.flash_name = Some("success".to_string()); - let flash_msg = "New password is now saved. Return home to login".to_string(); - context.flash_msg = Some(flash_msg); - Template::render("settings/admin/reset_password", &context) - } - Err(err) => { - let mut context = ChangePasswordContext::build(); - // set back icon link to network route - context.back = Some("/".to_string()); - context.title = Some("Reset Password".to_string()); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(format!("Failed to reset password: {}", err)); - Template::render("settings/admin/reset_password", &context) - } - } + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Reset Password".to_string())); + + let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) { + Ok(_) => ( + "success".to_string(), + "New password is now saved. Return home to login".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!("Failed to reset password: {}", err), + ), + }; + + context.insert("flash_name", &Some(flash_name)); + context.insert("flash_msg", &Some(flash_msg)); + + Template::render("settings/admin/reset_password", &context.into_json()) } // HELPERS AND ROUTES FOR /send_password_reset -#[derive(Debug, Serialize)] -pub struct SendPasswordResetContext { - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl SendPasswordResetContext { - pub fn build() -> SendPasswordResetContext { - SendPasswordResetContext { - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - /// Page for users who have forgotten their password. /// This route is used by a user who is not logged in /// to initiate the sending of a new password reset. #[get("/forgot_password")] pub fn forgot_password_page(flash: Option) -> Template { - let mut context = SendPasswordResetContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Send Password Reset".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Send Password Reset".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("settings/admin/forgot_password", &context) + + Template::render("settings/admin/forgot_password", &context.into_json()) } /// Send password reset request handler. This route is used by a user who is not logged in @@ -301,27 +230,25 @@ pub fn forgot_password_page(flash: Option) -> Template { #[post("/send_password_reset")] pub fn send_password_reset_post() -> Template { info!("++ send password reset post"); - let result = password_utils::send_password_reset(); - match result { - Ok(_) => { - let mut context = ChangePasswordContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Send Password Reset".to_string()); - context.flash_name = Some("success".to_string()); - let flash_msg = - "A password reset link has been sent to the admin of this device".to_string(); - context.flash_msg = Some(flash_msg); - Template::render("settings/admin/forgot_password", &context) - } - Err(err) => { - let mut context = ChangePasswordContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Send Password Reset".to_string()); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(format!("Failed to send password reset link: {}", err)); - Template::render("settings/admin/forgot_password", &context) - } - } + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Send Password Reset".to_string())); + + let (flash_name, flash_msg) = match password_utils::send_password_reset() { + Ok(_) => ( + "success".to_string(), + "A password reset link has been sent to the admin of this device".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!("Failed to send password reset link: {}", err), + ), + }; + + context.insert("flash_name", &Some(flash_name)); + context.insert("flash_msg", &Some(flash_msg)); + + Template::render("settings/admin/forgot_password", &context.into_json()) } // HELPERS AND ROUTES FOR /settings/change_password @@ -353,42 +280,40 @@ pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebErr /// Change password request handler. This is used by a user who is already logged in. #[get("/change_password")] pub fn change_password(flash: Option, _auth: Authenticated) -> Template { - let mut context = ChangePasswordContext::build(); - // set back icon link to network route - context.back = Some("/settings/admin".to_string()); - context.title = Some("Change Password".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/settings/admin".to_string())); + context.insert("title", &Some("Change Password".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("settings/admin/change_password", &context) + + Template::render("settings/admin/change_password", &context.into_json()) } /// Change password form request handler. This route is used by a user who is already logged in. #[post("/change_password", data = "")] pub fn change_password_post(password_form: Form, _auth: Authenticated) -> Template { - let result = save_password_form(password_form.into_inner()); - match result { - Ok(_) => { - let mut context = ChangePasswordContext::build(); - // set back icon link to network route - context.back = Some("/settings/admin".to_string()); - context.title = Some("Change Password".to_string()); - context.flash_name = Some("success".to_string()); - context.flash_msg = Some("New password is now saved".to_string()); - // template_dir is set in Rocket.toml - Template::render("settings/admin/change_password", &context) - } - Err(err) => { - let mut context = ChangePasswordContext::build(); - // set back icon link to network route - context.back = Some("/settings/admin".to_string()); - context.title = Some("Change Password".to_string()); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(format!("Failed to save new password: {}", err)); - Template::render("settings/admin/change_password", &context) - } - } + let mut context = Context::new(); + context.insert("back", &Some("/settings/admin".to_string())); + context.insert("title", &Some("Change Password".to_string())); + + let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) { + Ok(_) => ( + "success".to_string(), + "New password is now saved".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!("Failed to save new password: {}", err), + ), + }; + + context.insert("flash_name", &Some(flash_name)); + context.insert("flash_msg", &Some(flash_msg)); + + Template::render("settings/admin/change_password", &context.into_json()) } diff --git a/peach-web/src/routes/index.rs b/peach-web/src/routes/index.rs index aa1113e..11d1f23 100644 --- a/peach-web/src/routes/index.rs +++ b/peach-web/src/routes/index.rs @@ -1,69 +1,33 @@ use rocket::{get, request::FlashMessage}; -use rocket_dyn_templates::Template; -use serde::Serialize; +use rocket_dyn_templates::{tera::Context, Template}; use crate::routes::authentication::Authenticated; // HELPERS AND ROUTES FOR / (HOME PAGE) -#[derive(Debug, Serialize)] -pub struct HomeContext { - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, -} - -impl HomeContext { - pub fn build() -> HomeContext { - HomeContext { - flash_name: None, - flash_msg: None, - title: None, - } - } -} - #[get("/")] pub fn home(_auth: Authenticated) -> Template { - let context = HomeContext { - flash_name: None, - flash_msg: None, - title: None, - }; - Template::render("home", &context) + let mut context = Context::new(); + context.insert("flash_name", &None::<()>); + context.insert("flash_msg", &None::<()>); + context.insert("title", &None::<()>); + + Template::render("home", &context.into_json()) } // HELPERS AND ROUTES FOR /help -#[derive(Debug, Serialize)] -pub struct HelpContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, -} - -impl HelpContext { - pub fn build() -> HelpContext { - HelpContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - } - } -} - #[get("/help")] pub fn help(flash: Option) -> Template { - let mut context = HelpContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Help".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Help".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("help", &context) + + Template::render("settings/menu", &context.into_json()) } diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs index 118fa0a..8dca817 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs @@ -1,95 +1,69 @@ -use rocket::serde::{Deserialize, Serialize}; use rocket::{ form::{Form, FromForm}, get, post, request::FlashMessage, response::{Flash, Redirect}, + serde::Deserialize, uri, }; -use rocket_dyn_templates::Template; +use rocket_dyn_templates::{tera::Context, Template}; use peach_lib::config_manager; -use peach_lib::config_manager::load_peach_config; use crate::error::PeachWebError; use crate::routes::authentication::Authenticated; // HELPERS AND ROUTES FOR /settings/admin -#[derive(Debug, Serialize)] -pub struct AdminMenuContext { - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl AdminMenuContext { - pub fn build() -> AdminMenuContext { - AdminMenuContext { - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - /// Administrator settings menu. #[get("/")] pub fn admin_menu(flash: Option, _auth: Authenticated) -> Template { - let mut context = AdminMenuContext::build(); - // set back icon link to settings route - context.back = Some("/settings".to_string()); - context.title = Some("Administrator Settings".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/settings".to_string())); + context.insert("title", &Some("Administrator Settings".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("settings/admin/menu", &context) + + Template::render("settings/admin/menu", &context.into_json()) } // HELPERS AND ROUTES FOR /settings/admin/configure -#[derive(Debug, Serialize)] -pub struct ConfigureAdminContext { - pub ssb_admin_ids: Vec, - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl ConfigureAdminContext { - pub fn build() -> ConfigureAdminContext { - let peach_config = load_peach_config().unwrap(); - let ssb_admin_ids = peach_config.ssb_admin_ids; - ConfigureAdminContext { - ssb_admin_ids, - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - /// View and delete currently configured admin. #[get("/configure")] pub fn configure_admin(flash: Option, _auth: Authenticated) -> Template { - let mut context = ConfigureAdminContext::build(); - // set back icon link to settings route - context.back = Some("/settings/admin".to_string()); - context.title = Some("Configure Admin".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/settings/admin".to_string())); + context.insert("title", &Some("Configure Admin".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("settings/admin/configure_admin", &context) + + // load the peach configuration vector + match config_manager::load_peach_config() { + Ok(config) => { + // retrieve the vector of ssb admin ids + let ssb_admin_ids = config.ssb_admin_ids; + context.insert("ssb_admin_ids", &ssb_admin_ids); + } + // if load fails, overwrite the flash_name and flash_msg + Err(e) => { + context.insert("flash_name", &Some("error".to_string())); + context.insert( + "flash_msg", + &Some(format!("Failed to load Peach config: {}", e)), + ); + } + } + + Template::render("settings/admin/configure_admin", &context.into_json()) } // HELPERS AND ROUTES FOR /settings/admin/add @@ -99,25 +73,6 @@ pub struct AddAdminForm { pub ssb_id: String, } -#[derive(Debug, Serialize)] -pub struct AddAdminContext { - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl AddAdminContext { - pub fn build() -> AddAdminContext { - AddAdminContext { - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> { let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?; // if the previous line didn't throw an error then it was a success @@ -126,17 +81,18 @@ pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError #[get("/add")] pub fn add_admin(flash: Option, _auth: Authenticated) -> Template { - let mut context = AddAdminContext::build(); - context.back = Some("/settings/admin/configure".to_string()); - context.title = Some("Add Admin".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/settings/admin/configure".to_string())); + context.insert("title", &Some("Add Admin".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; + // template_dir is set in Rocket.toml - Template::render("settings/admin/add_admin", &context) + Template::render("settings/admin/add_admin", &context.into_json()) } #[post("/add", data = "")] diff --git a/peach-web/src/routes/settings/menu.rs b/peach-web/src/routes/settings/menu.rs index e5abe1c..bb7db5d 100644 --- a/peach-web/src/routes/settings/menu.rs +++ b/peach-web/src/routes/settings/menu.rs @@ -1,41 +1,22 @@ -use rocket::{get, request::FlashMessage, serde::Serialize}; -use rocket_dyn_templates::Template; +use rocket::{get, request::FlashMessage}; +use rocket_dyn_templates::{tera::Context, Template}; use crate::routes::authentication::Authenticated; // HELPERS AND ROUTES FOR /settings -#[derive(Debug, Serialize)] -pub struct SettingsMenuContext { - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl SettingsMenuContext { - pub fn build() -> SettingsMenuContext { - SettingsMenuContext { - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - /// View and delete currently configured admin. #[get("/settings")] pub fn settings_menu(flash: Option, _auth: Authenticated) -> Template { - let mut context = SettingsMenuContext::build(); - // set back icon link to network route - context.back = Some("/".to_string()); - context.title = Some("Settings".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Settings".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("settings/menu", &context) + + Template::render("settings/menu", &context.into_json()) } diff --git a/peach-web/src/routes/settings/network.rs b/peach-web/src/routes/settings/network.rs index 2444f3a..2d4f859 100644 --- a/peach-web/src/routes/settings/network.rs +++ b/peach-web/src/routes/settings/network.rs @@ -1,5 +1,6 @@ -use log::{debug, warn}; +use std::collections::HashMap; +use log::{debug, warn}; use rocket::{ form::{Form, FromForm}, get, post, @@ -9,14 +10,14 @@ use rocket::{ uri, UriDisplayQuery, }; use rocket_dyn_templates::Template; -use std::collections::HashMap; -use peach_lib::network_client; -use peach_lib::network_client::{AccessPoint, Networks, Scan}; -use peach_lib::stats_client::Traffic; +use peach_lib::{ + // TODO: replace this with peach_network::network + network_client::{AccessPoint, Networks, Scan}, + stats_client::Traffic, +}; use crate::routes::authentication::Authenticated; -use crate::utils::monitor; use crate::utils::monitor::{Alert, Data, Threshold}; // STRUCTS USED BY NETWORK ROUTES @@ -575,6 +576,7 @@ pub fn deploy_client(_auth: Authenticated) -> Flash { #[get("/wifi/add")] pub fn add_wifi(flash: Option, _auth: Authenticated) -> Template { + // TODO: use simple context (no need to build entire NetworkContext) let mut context = NetworkContext::build(); // set back icon link to network route context.back = Some("/settings/network".to_string()); From d8c40e072486b133efe3ae805bdae22d2a5f443a Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 13 Jan 2022 15:47:14 +0200 Subject: [PATCH 08/18] move context builders into dedicated directory --- peach-web/src/context/dns.rs | 36 +++ peach-web/src/context/mod.rs | 2 + peach-web/src/context/network.rs | 398 +++++++++++++++++++++++++++++++ 3 files changed, 436 insertions(+) create mode 100644 peach-web/src/context/dns.rs create mode 100644 peach-web/src/context/mod.rs create mode 100644 peach-web/src/context/network.rs diff --git a/peach-web/src/context/dns.rs b/peach-web/src/context/dns.rs new file mode 100644 index 0000000..9d74ced --- /dev/null +++ b/peach-web/src/context/dns.rs @@ -0,0 +1,36 @@ +use peach_lib::{config_manager, dyndns_client}; +use rocket::serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct ConfigureDNSContext { + pub external_domain: String, + pub dyndns_subdomain: String, + pub enable_dyndns: bool, + pub is_dyndns_online: bool, + pub back: Option, + pub title: Option, + pub flash_name: Option, + pub flash_msg: Option, +} + +impl ConfigureDNSContext { + pub fn build() -> ConfigureDNSContext { + // TODO: replace `unwrap` with resilient error handling + let peach_config = config_manager::load_peach_config().unwrap(); + let dyndns_fulldomain = peach_config.dyn_domain; + let is_dyndns_online = dyndns_client::is_dns_updater_online().unwrap(); + let dyndns_subdomain = + dyndns_client::get_dyndns_subdomain(&dyndns_fulldomain).unwrap_or(dyndns_fulldomain); + + ConfigureDNSContext { + external_domain: peach_config.external_domain, + dyndns_subdomain, + enable_dyndns: peach_config.dyn_enabled, + is_dyndns_online, + back: None, + title: None, + flash_name: None, + flash_msg: None, + } + } +} diff --git a/peach-web/src/context/mod.rs b/peach-web/src/context/mod.rs new file mode 100644 index 0000000..d2d350c --- /dev/null +++ b/peach-web/src/context/mod.rs @@ -0,0 +1,2 @@ +pub mod dns; +pub mod network; diff --git a/peach-web/src/context/network.rs b/peach-web/src/context/network.rs new file mode 100644 index 0000000..3ce1ee5 --- /dev/null +++ b/peach-web/src/context/network.rs @@ -0,0 +1,398 @@ +//! Data retrieval for the purpose of serving routes and hydrating +//! network-related HTML templates. + +use std::collections::HashMap; + +use rocket::{ + form::FromForm, + serde::{Deserialize, Serialize}, + UriDisplayQuery, +}; + +use peach_network::{ + network, + network::{Scan, Status, Traffic}, +}; + +use crate::{ + utils::{ + monitor, + monitor::{Alert, Data, Threshold}, + }, + AP_IFACE, WLAN_IFACE, +}; + +#[derive(Debug, Serialize)] +pub struct AccessPoint { + pub detail: Option, + pub signal: Option, + pub state: String, +} + +pub fn ap_state() -> String { + match network::state(&*AP_IFACE) { + Ok(Some(state)) => state, + _ => "Interface unavailable".to_string(), + } +} + +#[derive(Debug, Deserialize, FromForm, UriDisplayQuery)] +pub struct Ssid { + pub ssid: String, +} + +#[derive(Debug, Deserialize, FromForm)] +pub struct WiFi { + pub ssid: String, + pub pass: String, +} + +fn convert_traffic(traffic: Traffic) -> Option { + // modify traffic values & assign measurement unit + // based on received and transmitted values + let (rx, rx_unit) = if traffic.received > 1_047_527_424 { + // convert to GB + (traffic.received / 1_073_741_824, "GB".to_string()) + } else if traffic.received > 0 { + // otherwise, convert it to MB + ((traffic.received / 1024) / 1024, "MB".to_string()) + } else { + (0, "MB".to_string()) + }; + + let (tx, tx_unit) = if traffic.transmitted > 1_047_527_424 { + // convert to GB + (traffic.transmitted / 1_073_741_824, "GB".to_string()) + } else if traffic.transmitted > 0 { + ((traffic.transmitted / 1024) / 1024, "MB".to_string()) + } else { + (0, "MB".to_string()) + }; + + Some(IfaceTraffic { + rx, + rx_unit, + tx, + tx_unit, + }) +} + +#[derive(Debug, Serialize)] +pub struct IfaceTraffic { + pub rx: u64, + pub rx_unit: String, + pub tx: u64, + pub tx_unit: String, +} + +#[derive(Debug, Serialize)] +pub struct NetworkAlertContext { + pub alert: Alert, + pub back: Option, + pub data_total: Option, // combined stored and current wifi traffic in bytes + pub flash_name: Option, + pub flash_msg: Option, + pub threshold: Threshold, + pub title: Option, + pub traffic: Option, // current wifi traffic in bytes (since boot) +} + +impl NetworkAlertContext { + pub fn build() -> NetworkAlertContext { + let alert = monitor::get_alerts().unwrap(); + // stored wifi data values as bytes + let stored_traffic = monitor::get_data().unwrap(); + let threshold = monitor::get_thresholds().unwrap(); + + let (traffic, data_total) = match network::traffic(&*WLAN_IFACE) { + // convert bytes to mb or gb and add appropriate units + Ok(Some(t)) => { + let current_traffic = t.received + t.transmitted; + let traffic = convert_traffic(t); + let total = stored_traffic.total + current_traffic; + let data_total = Data { total }; + (traffic, Some(data_total)) + } + _ => (None, None), + }; + + NetworkAlertContext { + alert, + back: None, + data_total, + flash_name: None, + flash_msg: None, + threshold, + title: None, + traffic, + } + } +} + +#[derive(Debug, Serialize)] +pub struct NetworkDetailContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub selected: Option, + pub title: Option, + pub saved_aps: Vec, + pub wlan_ip: String, + pub wlan_networks: HashMap, + pub wlan_rssi: Option, + pub wlan_ssid: String, + pub wlan_state: String, + pub wlan_status: Option, + pub wlan_traffic: Option, +} + +impl NetworkDetailContext { + pub fn build() -> NetworkDetailContext { + let wlan_ip = match network::ip(&*WLAN_IFACE) { + Ok(Some(ip)) => ip, + _ => "x.x.x.x".to_string(), + }; + + // list of networks saved in wpa_supplicant.conf + let wlan_list = match network::saved_networks() { + Ok(Some(ssids)) => ssids, + _ => Vec::new(), + }; + + // list of networks saved in wpa_supplicant.conf + let saved_aps = wlan_list.clone(); + + let wlan_rssi = match network::rssi_percent(&*WLAN_IFACE) { + Ok(rssi) => rssi, + Err(_) => None, + }; + + // list of networks currently in range (online & accessible) + let wlan_scan = match network::available_networks(&*WLAN_IFACE) { + Ok(Some(networks)) => networks, + _ => Vec::new(), + }; + + let wlan_ssid = match network::ssid(&*WLAN_IFACE) { + Ok(Some(ssid)) => ssid, + _ => "Not connected".to_string(), + }; + + let wlan_state = match network::state(&*WLAN_IFACE) { + Ok(Some(state)) => state, + _ => "Interface unavailable".to_string(), + }; + + let wlan_status = match network::status(&*WLAN_IFACE) { + Ok(status) => status, + // interface unavailable + _ => None, + }; + + let wlan_traffic = match network::traffic(&*WLAN_IFACE) { + // convert bytes to mb or gb and add appropriate units + Ok(Some(traffic)) => convert_traffic(traffic), + _ => None, + }; + + // create a hashmap to combine wlan_list & wlan_scan without repetition + let mut wlan_networks = HashMap::new(); + + for ap in wlan_scan { + let ssid = ap.ssid.clone(); + let rssi = ap.signal_level.clone(); + // parse the string to a signed integer (for math) + let rssi_parsed = rssi.parse::().unwrap(); + // perform rssi (dBm) to quality (%) conversion + let quality_percent = 2 * (rssi_parsed + 100); + let ap_detail = AccessPoint { + detail: Some(ap), + state: "Available".to_string(), + signal: Some(quality_percent), + }; + wlan_networks.insert(ssid, ap_detail); + } + + for network in wlan_list { + // avoid repetition by checking that ssid is not already in list + if !wlan_networks.contains_key(&network) { + let ssid = network.clone(); + let net_detail = AccessPoint { + detail: None, + state: "Not in range".to_string(), + signal: None, + }; + wlan_networks.insert(ssid, net_detail); + } + } + + NetworkDetailContext { + back: None, + flash_name: None, + flash_msg: None, + selected: None, + title: None, + saved_aps, + wlan_ip, + wlan_networks, + wlan_rssi, + wlan_ssid, + wlan_state, + wlan_status, + wlan_traffic, + } + } +} + +#[derive(Debug, Serialize)] +pub struct NetworkListContext { + pub ap_state: String, + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub wlan_networks: HashMap, + pub wlan_ssid: String, +} + +impl NetworkListContext { + pub fn build() -> NetworkListContext { + // list of networks saved in wpa_supplicant.conf + let wlan_list = match network::saved_networks() { + Ok(Some(ssids)) => ssids, + _ => Vec::new(), + }; + + // list of networks currently in range (online & accessible) + let wlan_scan = match network::available_networks(&*WLAN_IFACE) { + Ok(Some(networks)) => networks, + _ => Vec::new(), + }; + + let wlan_ssid = match network::ssid(&*WLAN_IFACE) { + Ok(Some(ssid)) => ssid, + _ => "Not connected".to_string(), + }; + + // create a hashmap to combine wlan_list & wlan_scan without repetition + let mut wlan_networks = HashMap::new(); + for ap in wlan_scan { + wlan_networks.insert(ap.ssid, "Available".to_string()); + } + for network in wlan_list { + // insert ssid (with state) only if it doesn't already exist + wlan_networks + .entry(network) + .or_insert_with(|| "Not in range".to_string()); + } + + let ap_state = match network::state(&*AP_IFACE) { + Ok(Some(state)) => state, + _ => "Interface unavailable".to_string(), + }; + + NetworkListContext { + ap_state, + back: None, + flash_msg: None, + flash_name: None, + title: None, + wlan_networks, + wlan_ssid, + } + } +} + +#[derive(Debug, Serialize)] +pub struct NetworkStatusContext { + pub ap_ip: String, + pub ap_ssid: String, + pub ap_state: String, + pub ap_traffic: Option, + pub wlan_ip: String, + pub wlan_rssi: Option, + pub wlan_ssid: String, + pub wlan_state: String, + pub wlan_status: Option, + pub wlan_traffic: Option, + pub flash_name: Option, + pub flash_msg: Option, + // passing in the ssid of a chosen access point + pub selected: Option, + pub title: Option, + pub back: Option, +} + +impl NetworkStatusContext { + pub fn build() -> Self { + let ap_ip = match network::ip(&*AP_IFACE) { + Ok(Some(ip)) => ip, + _ => "x.x.x.x".to_string(), + }; + + let ap_ssid = match network::ssid(&*AP_IFACE) { + Ok(Some(ssid)) => ssid, + _ => "Not currently activated".to_string(), + }; + + let ap_state = match network::state(&*AP_IFACE) { + Ok(Some(state)) => state, + _ => "Interface unavailable".to_string(), + }; + + let ap_traffic = match network::traffic(&*AP_IFACE) { + // convert bytes to mb or gb and add appropriate units + Ok(Some(traffic)) => convert_traffic(traffic), + _ => None, + }; + + let wlan_ip = match network::ip(&*WLAN_IFACE) { + Ok(Some(ip)) => ip, + _ => "x.x.x.x".to_string(), + }; + + let wlan_rssi = match network::rssi_percent(&*WLAN_IFACE) { + Ok(rssi) => rssi, + _ => None, + }; + + let wlan_ssid = match network::ssid(&*WLAN_IFACE) { + Ok(Some(ssid)) => ssid, + _ => "Not connected".to_string(), + }; + + let wlan_state = match network::state(&*WLAN_IFACE) { + Ok(Some(state)) => state, + _ => "Interface unavailable".to_string(), + }; + + let wlan_status = match network::status(&*WLAN_IFACE) { + Ok(status) => status, + _ => None, + }; + + let wlan_traffic = match network::traffic(&*WLAN_IFACE) { + // convert bytes to mb or gb and add appropriate units + Ok(Some(traffic)) => convert_traffic(traffic), + _ => None, + }; + + NetworkStatusContext { + ap_ip, + ap_ssid, + ap_state, + ap_traffic, + wlan_ip, + wlan_rssi, + wlan_ssid, + wlan_state, + wlan_status, + wlan_traffic, + flash_name: None, + flash_msg: None, + selected: None, + title: None, + back: None, + } + } +} From 60a0d7f293632299901f2c81c8b4141f3c67bdc3 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 13 Jan 2022 15:47:43 +0200 Subject: [PATCH 09/18] set global vars for iface names --- peach-web/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 92528d6..56c8f75 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -24,6 +24,7 @@ #![feature(proc_macro_hygiene, decl_macro)] +mod context; pub mod error; mod router; pub mod routes; @@ -48,6 +49,9 @@ lazy_static! { }; } +static WLAN_IFACE: &str = "wlan0"; +static AP_IFACE: &str = "ap0"; + pub fn init_rocket() -> Rocket { info!("Initializing Rocket"); if *STANDALONE_MODE { From a5f0d991faf7132279e95af82d14ab1e14675d1f Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 13 Jan 2022 15:48:55 +0200 Subject: [PATCH 10/18] fix template rendering for help --- peach-web/src/routes/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/routes/index.rs b/peach-web/src/routes/index.rs index 11d1f23..87d3d4f 100644 --- a/peach-web/src/routes/index.rs +++ b/peach-web/src/routes/index.rs @@ -29,5 +29,5 @@ pub fn help(flash: Option) -> Template { context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("settings/menu", &context.into_json()) + Template::render("help", &context.into_json()) } From 166f4d25ae6fad2ce90ca0a163432d7de1157df7 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 13 Jan 2022 15:49:12 +0200 Subject: [PATCH 11/18] move context objects and builders to dedicated directory --- peach-web/src/routes/settings/dns.rs | 64 +-- peach-web/src/routes/settings/network.rs | 620 ++++------------------- peach-web/src/routes/status/network.rs | 149 +----- 3 files changed, 110 insertions(+), 723 deletions(-) diff --git a/peach-web/src/routes/settings/dns.rs b/peach-web/src/routes/settings/dns.rs index 9e1556e..1694ec3 100644 --- a/peach-web/src/routes/settings/dns.rs +++ b/peach-web/src/routes/settings/dns.rs @@ -3,7 +3,7 @@ use rocket::{ form::{Form, FromForm}, get, post, request::FlashMessage, - serde::{Deserialize, Serialize}, + serde::Deserialize, }; use rocket_dyn_templates::Template; @@ -14,8 +14,9 @@ use peach_lib::{ jsonrpc_core::types::error::ErrorCode, }; -use crate::error::PeachWebError; -use crate::routes::authentication::Authenticated; +use crate::{ + context::dns::ConfigureDNSContext, error::PeachWebError, routes::authentication::Authenticated, +}; #[derive(Debug, Deserialize, FromForm)] pub struct DnsForm { @@ -73,40 +74,6 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> { } } -#[derive(Debug, Serialize)] -pub struct ConfigureDNSContext { - pub external_domain: String, - pub dyndns_subdomain: String, - pub enable_dyndns: bool, - pub is_dyndns_online: bool, - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, -} - -impl ConfigureDNSContext { - pub fn build() -> ConfigureDNSContext { - // TODO: replace `unwrap` with resilient error handling - let peach_config = config_manager::load_peach_config().unwrap(); - let dyndns_fulldomain = peach_config.dyn_domain; - let is_dyndns_online = dyndns_client::is_dns_updater_online().unwrap(); - let dyndns_subdomain = - dyndns_client::get_dyndns_subdomain(&dyndns_fulldomain).unwrap_or(dyndns_fulldomain); - - ConfigureDNSContext { - external_domain: peach_config.external_domain, - dyndns_subdomain, - enable_dyndns: peach_config.dyn_enabled, - is_dyndns_online, - back: None, - title: None, - flash_name: None, - flash_msg: None, - } - } -} - #[get("/dns")] pub fn configure_dns(flash: Option, _auth: Authenticated) -> Template { let mut context = ConfigureDNSContext::build(); @@ -128,28 +95,23 @@ pub fn configure_dns(flash: Option, _auth: Authenticated) -> Templ #[post("/dns", data = "")] pub fn configure_dns_post(dns: Form, _auth: Authenticated) -> Template { let result = save_dns_configuration(dns.into_inner()); + + let mut context = ConfigureDNSContext::build(); + + // set back icon link to network route + context.back = Some("/settings/network".to_string()); + context.title = Some("Configure DNS".to_string()); + match result { Ok(_) => { - let mut context = ConfigureDNSContext::build(); - - // set back icon link to network route - context.back = Some("/settings/network".to_string()); - context.title = Some("Configure DNS".to_string()); context.flash_name = Some("success".to_string()); context.flash_msg = Some("New dynamic dns configuration is now enabled".to_string()); - - Template::render("settings/network/configure_dns", &context) } Err(err) => { - let mut context = ConfigureDNSContext::build(); - - // set back icon link to network route - context.back = Some("/settings/network".to_string()); - context.title = Some("Configure DNS".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some(format!("Failed to save dns configurations: {}", err)); - - Template::render("settings/network/configure_dns", &context) } } + + Template::render("settings/network/configure_dns", &context) } diff --git a/peach-web/src/routes/settings/network.rs b/peach-web/src/routes/settings/network.rs index 2d4f859..b913c16 100644 --- a/peach-web/src/routes/settings/network.rs +++ b/peach-web/src/routes/settings/network.rs @@ -1,25 +1,24 @@ -use std::collections::HashMap; - use log::{debug, warn}; use rocket::{ form::{Form, FromForm}, get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::{Deserialize, Serialize}, + serde::Deserialize, uri, UriDisplayQuery, }; -use rocket_dyn_templates::Template; +use rocket_dyn_templates::{tera::Context, Template}; -use peach_lib::{ - // TODO: replace this with peach_network::network - network_client::{AccessPoint, Networks, Scan}, - stats_client::Traffic, +use peach_network::network; + +use crate::{ + context, + context::network::{NetworkAlertContext, NetworkDetailContext, NetworkListContext}, + routes::authentication::Authenticated, + utils::{monitor, monitor::Threshold}, + AP_IFACE, WLAN_IFACE, }; -use crate::routes::authentication::Authenticated; -use crate::utils::monitor::{Alert, Data, Threshold}; - // STRUCTS USED BY NETWORK ROUTES #[derive(Debug, Deserialize, FromForm, UriDisplayQuery)] @@ -51,12 +50,12 @@ pub fn wifi_usage_reset(_auth: Authenticated) -> Flash { pub fn connect_wifi(network: Form, _auth: Authenticated) -> Flash { let ssid = &network.ssid; let url = uri!(network_detail(ssid = ssid)); - match network_client::id("wlan0", ssid) { - Ok(id) => match network_client::connect(&id, "wlan0") { + match network::id(&*WLAN_IFACE, ssid) { + Ok(Some(id)) => match network::connect(&id, &*WLAN_IFACE) { Ok(_) => Flash::success(Redirect::to(url), "Connected to chosen network"), Err(_) => Flash::error(Redirect::to(url), "Failed to connect to chosen network"), }, - Err(_) => Flash::error(Redirect::to(url), "Failed to retrieve the network ID"), + _ => Flash::error(Redirect::to(url), "Failed to retrieve the network ID"), } } @@ -64,7 +63,7 @@ pub fn connect_wifi(network: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash { let ssid = &network.ssid; let url = uri!(network_home); - match network_client::disable("wlan0", ssid) { + match network::disable(&*WLAN_IFACE, ssid) { Ok(_) => Flash::success(Redirect::to(url), "Disconnected from WiFi network"), Err(_) => Flash::error(Redirect::to(url), "Failed to disconnect from WiFi network"), } @@ -74,7 +73,7 @@ pub fn disconnect_wifi(network: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash { let ssid = &network.ssid; let url = uri!(network_home); - match network_client::forget("wlan0", ssid) { + match network::forget(&*WLAN_IFACE, ssid) { Ok(_) => Flash::success(Redirect::to(url), "WiFi credentials removed"), Err(_) => Flash::error( Redirect::to(url), @@ -85,21 +84,19 @@ pub fn forget_wifi(network: Form, _auth: Authenticated) -> Flash #[get("/wifi/modify?")] pub fn wifi_password(ssid: &str, flash: Option, _auth: Authenticated) -> Template { - let mut context = NetworkAddContext { - back: Some("/settings/network/wifi".to_string()), - flash_name: None, - flash_msg: None, - selected: Some(ssid.to_string()), - title: Some("Update WiFi Password".to_string()), - }; + let mut context = Context::new(); + context.insert("back", &Some("/settings/network/wifi".to_string())); + context.insert("title", &Some("Update WiFi Password".to_string())); + context.insert("selected", &Some(ssid.to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - // template_dir is set in Rocket.toml - Template::render("settings/network/modify_ap", &context) + + Template::render("settings/network/modify_ap", &context.into_json()) } #[post("/wifi/modify", data = "")] @@ -107,7 +104,7 @@ pub fn wifi_set_password(wifi: Form, _auth: Authenticated) -> Flash Flash::success(Redirect::to(url), "WiFi password updated".to_string()), Err(_) => Flash::error( Redirect::to(url), @@ -118,174 +115,23 @@ pub fn wifi_set_password(wifi: Form, _auth: Authenticated) -> Flash, - pub wlan_ip: String, - pub wlan_rssi: Option, - pub wlan_scan: Option>, - pub wlan_ssid: String, - pub wlan_state: String, - pub wlan_status: String, - pub wlan_traffic: Option, - pub flash_name: Option, - pub flash_msg: Option, - // allows for passing in the ssid of a chosen access point - // this is used in the network_detail template - pub selected: Option, - // page title for header in navbar - pub title: Option, - // url for back-arrow link - pub back: Option, -} - -impl NetworkContext { - pub fn build() -> NetworkContext { - let ap_ip = match network_client::ip("ap0") { - Ok(ip) => ip, - Err(_) => "x.x.x.x".to_string(), - }; - let ap_ssid = match network_client::ssid("ap0") { - Ok(ssid) => ssid, - Err(_) => "Not currently activated".to_string(), - }; - let ap_state = match network_client::state("ap0") { - Ok(state) => state, - Err(_) => "Interface unavailable".to_string(), - }; - let ap_traffic = match network_client::traffic("ap0") { - Ok(traffic) => { - let mut t = traffic; - // modify traffic values & assign measurement unit - // based on received and transmitted values - // if received > 999 MB, convert it to GB - if t.received > 1_047_527_424 { - t.received /= 1_073_741_824; - t.rx_unit = Some("GB".to_string()); - } else if t.received > 0 { - // otherwise, convert it to MB - t.received = (t.received / 1024) / 1024; - t.rx_unit = Some("MB".to_string()); - } else { - t.received = 0; - t.rx_unit = Some("MB".to_string()); - } - - if t.transmitted > 1_047_527_424 { - t.transmitted /= 1_073_741_824; - t.tx_unit = Some("GB".to_string()); - } else if t.transmitted > 0 { - t.transmitted = (t.transmitted / 1024) / 1024; - t.tx_unit = Some("MB".to_string()); - } else { - t.transmitted = 0; - t.tx_unit = Some("MB".to_string()); - } - Some(t) - } - Err(_) => None, - }; - let wlan_ip = match network_client::ip("wlan0") { - Ok(ip) => ip, - Err(_) => "x.x.x.x".to_string(), - }; - let wlan_rssi = match network_client::rssi_percent("wlan0") { - Ok(rssi) => Some(rssi), - Err(_) => None, - }; - let wlan_scan = match network_client::available_networks("wlan0") { - Ok(networks) => { - let scan: Vec = serde_json::from_str(networks.as_str()) - .expect("Failed to deserialize scan_networks response"); - Some(scan) - } - Err(_) => None, - }; - let wlan_ssid = match network_client::ssid("wlan0") { - Ok(ssid) => ssid, - Err(_) => "Not connected".to_string(), - }; - let wlan_state = match network_client::state("wlan0") { - Ok(state) => state, - Err(_) => "Interface unavailable".to_string(), - }; - let wlan_status = match network_client::status("wlan0") { - Ok(status) => status, - Err(_) => "Interface unavailable".to_string(), - }; - let wlan_traffic = match network_client::traffic("wlan0") { - Ok(traffic) => { - let mut t = traffic; - // modify traffic values & assign measurement unit - // based on received and transmitted values - // if received > 999 MB, convert it to GB - if t.received > 1_047_527_424 { - t.received /= 1_073_741_824; - t.rx_unit = Some("GB".to_string()); - } else if t.received > 0 { - // otherwise, convert it to MB - t.received = (t.received / 1024) / 1024; - t.rx_unit = Some("MB".to_string()); - } else { - t.received = 0; - t.rx_unit = Some("MB".to_string()); - } - - if t.transmitted > 1_047_527_424 { - t.transmitted /= 1_073_741_824; - t.tx_unit = Some("GB".to_string()); - } else if t.transmitted > 0 { - t.transmitted = (t.transmitted / 1024) / 1024; - t.tx_unit = Some("MB".to_string()); - } else { - t.transmitted = 0; - t.tx_unit = Some("MB".to_string()); - } - Some(t) - } - Err(_) => None, - }; - - NetworkContext { - ap_ip, - ap_ssid, - ap_state, - ap_traffic, - wlan_ip, - wlan_rssi, - wlan_scan, - wlan_ssid, - wlan_state, - wlan_status, - wlan_traffic, - flash_name: None, - flash_msg: None, - selected: None, - title: None, - back: None, - } - } -} - #[get("/")] pub fn network_home(flash: Option, _auth: Authenticated) -> Template { - // assign context through context_builder call - let mut context = NetworkContext::build(); - // set back button (nav) url - context.back = Some("/settings".to_string()); - // set page title - context.title = Some("Network Configuration".to_string()); + // assign context + let mut context = Context::new(); + context.insert("back", &Some("/settings")); + context.insert("title", &Some("Network Configuration")); + context.insert("ap_state", &context::network::ap_state()); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; + // template_dir is set in Rocket.toml - Template::render("settings/network/menu", &context) + Template::render("settings/network/menu", &context.into_json()) } // HELPERS AND ROUTES FOR /settings/network/ap/activate @@ -294,7 +140,7 @@ pub fn network_home(flash: Option, _auth: Authenticated) -> Templa pub fn deploy_ap(_auth: Authenticated) -> Flash { // activate the wireless access point debug!("Activating WiFi access point."); - match network_client::activate_ap() { + match network::start_iface_service(&*AP_IFACE) { Ok(_) => Flash::success( Redirect::to("/settings/network"), "Activated WiFi access point", @@ -308,252 +154,37 @@ pub fn deploy_ap(_auth: Authenticated) -> Flash { // HELPERS AND ROUTES FOR /settings/network/wifi -#[derive(Debug, Serialize)] -pub struct NetworkListContext { - pub ap_state: String, - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub wlan_networks: HashMap, - pub wlan_ssid: String, -} - -impl NetworkListContext { - pub fn build() -> NetworkListContext { - // list of networks saved in the wpa_supplicant.conf - let wlan_list = match network_client::saved_networks() { - Ok(ssids) => { - let networks: Vec = serde_json::from_str(ssids.as_str()) - .expect("Failed to deserialize scan_list response"); - networks - } - Err(_) => Vec::new(), - }; - - // list of networks currently in range (online & accessible) - let wlan_scan = match network_client::available_networks("wlan0") { - Ok(networks) => { - let scan: Vec = serde_json::from_str(networks.as_str()) - .expect("Failed to deserialize scan_networks response"); - scan - } - Err(_) => Vec::new(), - }; - - let wlan_ssid = match network_client::ssid("wlan0") { - Ok(ssid) => ssid, - Err(_) => "Not connected".to_string(), - }; - - // create a hashmap to combine wlan_list & wlan_scan without repetition - let mut wlan_networks = HashMap::new(); - for ap in wlan_scan { - wlan_networks.insert(ap.ssid, "Available".to_string()); - } - for network in wlan_list { - // insert ssid (with state) only if it doesn't already exist - wlan_networks - .entry(network.ssid) - .or_insert_with(|| "Not in range".to_string()); - } - - let ap_state = match network_client::state("ap0") { - Ok(state) => state, - Err(_) => "Interface unavailable".to_string(), - }; - - NetworkListContext { - ap_state, - back: None, - flash_msg: None, - flash_name: None, - title: None, - wlan_networks, - wlan_ssid, - } - } -} - #[get("/wifi")] pub fn wifi_list(flash: Option, _auth: Authenticated) -> Template { // assign context through context_builder call let mut context = NetworkListContext::build(); context.back = Some("/settings/network".to_string()); context.title = Some("WiFi Networks".to_string()); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - // template_dir is set in Rocket.toml + Template::render("settings/network/list_aps", &context) } // HELPERS AND ROUTES FOR /settings/network/wifi -#[derive(Debug, Serialize)] -pub struct NetworkDetailContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub saved_aps: Vec, - pub selected: Option, - pub title: Option, - pub wlan_ip: String, - pub wlan_networks: HashMap, - pub wlan_rssi: Option, - pub wlan_ssid: String, - pub wlan_state: String, - pub wlan_status: String, - pub wlan_traffic: Option, -} - -impl NetworkDetailContext { - pub fn build() -> NetworkDetailContext { - let wlan_ip = match network_client::ip("wlan0") { - Ok(ip) => ip, - Err(_) => "x.x.x.x".to_string(), - }; - // list of networks saved in wpa_supplicant.conf - let wlan_list = match network_client::saved_networks() { - Ok(ssids) => { - let networks: Vec = serde_json::from_str(ssids.as_str()) - .expect("Failed to deserialize scan_list response"); - networks - } - Err(_) => Vec::new(), - }; - // list of networks saved in wpa_supplicant.conf - // HACK: we're running the same function twice (wlan_list) - // see if we can implement clone for Vec instead - let saved_aps = match network_client::saved_networks() { - Ok(ssids) => { - let networks: Vec = serde_json::from_str(ssids.as_str()) - .expect("Failed to deserialize scan_list response"); - networks - } - Err(_) => Vec::new(), - }; - let wlan_rssi = match network_client::rssi_percent("wlan0") { - Ok(rssi) => Some(rssi), - Err(_) => None, - }; - // list of networks currently in range (online & accessible) - let wlan_scan = match network_client::available_networks("wlan0") { - Ok(networks) => { - let scan: Vec = serde_json::from_str(networks.as_str()) - .expect("Failed to deserialize scan_networks response"); - scan - } - Err(_) => Vec::new(), - }; - let wlan_ssid = match network_client::ssid("wlan0") { - Ok(ssid) => ssid, - Err(_) => "Not connected".to_string(), - }; - let wlan_state = match network_client::state("wlan0") { - Ok(state) => state, - Err(_) => "Interface unavailable".to_string(), - }; - let wlan_status = match network_client::status("wlan0") { - Ok(status) => status, - Err(_) => "Interface unavailable".to_string(), - }; - let wlan_traffic = match network_client::traffic("wlan0") { - Ok(traffic) => { - let mut t = traffic; - // modify traffic values & assign measurement unit - // based on received and transmitted values - // if received > 999 MB, convert it to GB - if t.received > 1_047_527_424 { - t.received /= 1_073_741_824; - t.rx_unit = Some("GB".to_string()); - } else if t.received > 0 { - // otherwise, convert it to MB - t.received = (t.received / 1024) / 1024; - t.rx_unit = Some("MB".to_string()); - } else { - t.received = 0; - t.rx_unit = Some("MB".to_string()); - } - - if t.transmitted > 1_047_527_424 { - t.transmitted /= 1_073_741_824; - t.tx_unit = Some("GB".to_string()); - } else if t.transmitted > 0 { - t.transmitted = (t.transmitted / 1024) / 1024; - t.tx_unit = Some("MB".to_string()); - } else { - t.transmitted = 0; - t.tx_unit = Some("MB".to_string()); - } - Some(t) - } - Err(_) => None, - }; - // create a hashmap to combine wlan_list & wlan_scan without repetition - let mut wlan_networks = HashMap::new(); - for ap in wlan_scan { - let ssid = ap.ssid.clone(); - let rssi = ap.signal_level.clone(); - // parse the string to a signed integer (for math) - let rssi_parsed = rssi.parse::().unwrap(); - // perform rssi (dBm) to quality (%) conversion - let quality_percent = 2 * (rssi_parsed + 100); - let ap_detail = AccessPoint { - detail: Some(ap), - state: "Available".to_string(), - signal: Some(quality_percent), - }; - wlan_networks.insert(ssid, ap_detail); - } - for network in wlan_list { - // avoid repetition by checking that ssid is not already in list - if !wlan_networks.contains_key(&network.ssid) { - let ssid = network.ssid.clone(); - let net_detail = AccessPoint { - detail: None, - state: "Not in range".to_string(), - signal: None, - }; - wlan_networks.insert(ssid, net_detail); - } - } - - NetworkDetailContext { - back: None, - flash_name: None, - flash_msg: None, - saved_aps, - selected: None, - title: None, - wlan_ip, - wlan_networks, - wlan_rssi, - wlan_ssid, - wlan_state, - wlan_status, - wlan_traffic, - } - } -} - #[get("/wifi?")] pub fn network_detail(ssid: &str, flash: Option, _auth: Authenticated) -> Template { - // assign context through context_builder call let mut context = NetworkDetailContext::build(); context.back = Some("/settings/network/wifi".to_string()); context.title = Some("WiFi Network".to_string()); context.selected = Some(ssid.to_string()); - // check to see if there is a flash message to display + if let Some(flash) = flash { - // add flash message contents to the context object context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - // template_dir is set in Rocket.toml + Template::render("settings/network/ap_details", &context) } @@ -563,7 +194,7 @@ pub fn network_detail(ssid: &str, flash: Option, _auth: Authentica pub fn deploy_client(_auth: Authenticated) -> Flash { // activate the wireless client debug!("Activating WiFi client mode."); - match network_client::activate_client() { + match network::start_iface_service(&*WLAN_IFACE) { Ok(_) => Flash::success(Redirect::to("/settings/network"), "Activated WiFi client"), Err(_) => Flash::error( Redirect::to("/settings/network"), @@ -576,153 +207,88 @@ pub fn deploy_client(_auth: Authenticated) -> Flash { #[get("/wifi/add")] pub fn add_wifi(flash: Option, _auth: Authenticated) -> Template { - // TODO: use simple context (no need to build entire NetworkContext) - let mut context = NetworkContext::build(); - // set back icon link to network route - context.back = Some("/settings/network".to_string()); - context.title = Some("Add WiFi Network".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/settings/network".to_string())); + context.insert("title", &Some("Add WiFi Network".to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - // template_dir is set in Rocket.toml - Template::render("settings/network/add_ap", &context) -} -// used in /settings/network/wifi/add? -#[derive(Debug, Serialize)] -pub struct NetworkAddContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub selected: Option, - pub title: Option, -} - -impl NetworkAddContext { - pub fn build() -> NetworkAddContext { - NetworkAddContext { - back: None, - flash_name: None, - flash_msg: None, - selected: None, - title: None, - } - } + Template::render("settings/network/add_ap", &context.into_json()) } #[get("/wifi/add?")] pub fn add_ssid(ssid: &str, flash: Option, _auth: Authenticated) -> Template { - let mut context = NetworkAddContext::build(); - context.back = Some("/settings/network/wifi".to_string()); - context.selected = Some(ssid.to_string()); - context.title = Some("Add WiFi Network".to_string()); + let mut context = Context::new(); + context.insert("back", &Some("/settings/network".to_string())); + context.insert("title", &Some("Add WiFi Network".to_string())); + context.insert("selected", &Some(ssid.to_string())); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - // template_dir is set in Rocket.toml - Template::render("settings/network/add_ap", &context) + + Template::render("settings/network/add_ap", &context.into_json()) } #[post("/wifi/add", data = "")] pub fn add_credentials(wifi: Form, _auth: Authenticated) -> Template { + let mut context = Context::new(); + context.insert("back", &Some("/settings/network".to_string())); + context.insert("title", &Some("Add WiFi Network".to_string())); + // check if the credentials already exist for this access point // note: this is nicer but it's an unstable feature: // if check_saved_aps(&wifi.ssid).contains(true) // use unwrap_or instead, set value to false if err is returned - let creds_exist = network_client::saved_ap(&wifi.ssid).unwrap_or(false); - if creds_exist { - let mut context = NetworkAddContext::build(); - context.back = Some("/settings/network".to_string()); - context.flash_name = Some("error".to_string()); - context.flash_msg = - Some("Network credentials already exist for this access point".to_string()); - context.title = Some("Add WiFi Network".to_string()); - // return early from handler with "creds already exist" message - return Template::render("settings/network/add_ap", &context); + //let creds_exist = network::saved_networks(&wifi.ssid).unwrap_or(false); + let creds_exist = match network::saved_networks() { + Ok(Some(networks)) => networks.contains(&wifi.ssid), + _ => false, }; // if credentials not found, generate and write wifi config to wpa_supplicant - match network_client::add(&wifi.ssid, &wifi.pass) { - Ok(_) => { - debug!("Added WiFi credentials."); - // force reread of wpa_supplicant.conf file with new credentials - match network_client::reconfigure() { - Ok(_) => debug!("Successfully reconfigured wpa_supplicant"), - Err(_) => warn!("Failed to reconfigure wpa_supplicant"), + let (flash_name, flash_msg) = if creds_exist { + ( + "error".to_string(), + "Network credentials already exist for this access point".to_string(), + ) + } else { + match network::add(&*WLAN_IFACE, &wifi.ssid, &wifi.pass) { + Ok(_) => { + debug!("Added WiFi credentials."); + // force reread of wpa_supplicant.conf file with new credentials + match network::reconfigure() { + Ok(_) => debug!("Successfully reconfigured wpa_supplicant"), + Err(_) => warn!("Failed to reconfigure wpa_supplicant"), + } + ("success".to_string(), "Added WiFi credentials".to_string()) + } + Err(_) => { + debug!("Failed to add WiFi credentials."); + ( + "error".to_string(), + "Failed to add WiFi credentials".to_string(), + ) } - let mut context = NetworkAddContext::build(); - context.back = Some("/settings/network".to_string()); - context.flash_name = Some("success".to_string()); - context.flash_msg = Some("Added WiFi credentials".to_string()); - context.title = Some("Add WiFi Network".to_string()); - Template::render("settings/network/add_ap", &context) } - Err(_) => { - debug!("Failed to add WiFi credentials."); - let mut context = NetworkAddContext::build(); - context.back = Some("/settings/network".to_string()); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some("Failed to add WiFi credentials".to_string()); - context.title = Some("Add WiFi Network".to_string()); - Template::render("settings/network/add_ap", &context) - } - } + }; + + context.insert("flash_name", &Some(flash_name)); + context.insert("flash_msg", &Some(flash_msg)); + + Template::render("settings/network/add_ap", &context.into_json()) } // HELPERS AND ROUTES FOR WIFI USAGE -#[derive(Debug, Serialize)] -pub struct NetworkAlertContext { - pub alert: Alert, - pub back: Option, - pub data_total: Data, // combined stored and current wifi traffic in bytes - pub flash_name: Option, - pub flash_msg: Option, - pub threshold: Threshold, - pub title: Option, - pub traffic: Traffic, // current wifi traffic in bytes (since boot) -} - -impl NetworkAlertContext { - pub fn build() -> NetworkAlertContext { - let alert = monitor::get_alerts().unwrap(); - // stored wifi data values as bytes - let stored_traffic = monitor::get_data().unwrap(); - let threshold = monitor::get_thresholds().unwrap(); - // current wifi traffic values as bytes - let traffic = match network_client::traffic("wlan0") { - Ok(t) => t, - Err(_) => Traffic { - received: 0, - transmitted: 0, - rx_unit: None, - tx_unit: None, - }, - }; - - let current_traffic = traffic.received + traffic.transmitted; - let total = stored_traffic.total + current_traffic; - let data_total = Data { total }; - - NetworkAlertContext { - alert, - back: None, - data_total, - flash_name: None, - flash_msg: None, - threshold, - title: None, - traffic, - } - } -} - #[get("/wifi/usage")] pub fn wifi_usage(flash: Option, _auth: Authenticated) -> Template { let mut context = NetworkAlertContext::build(); diff --git a/peach-web/src/routes/status/network.rs b/peach-web/src/routes/status/network.rs index aefa766..c75c0dc 100644 --- a/peach-web/src/routes/status/network.rs +++ b/peach-web/src/routes/status/network.rs @@ -1,162 +1,21 @@ use rocket::{get, request::FlashMessage}; use rocket_dyn_templates::Template; -use serde::Serialize; - -use peach_network::{ - network, - network::{Status, Traffic}, -}; +use crate::context::network::NetworkStatusContext; use crate::routes::authentication::Authenticated; // HELPERS AND ROUTES FOR /status/network -#[derive(Debug, Serialize)] -pub struct IfaceTraffic { - pub rx: u64, - pub rx_unit: Option, - pub tx: u64, - pub tx_unit: Option, -} - -impl IfaceTraffic { - fn default() -> Self { - IfaceTraffic { - rx: 0, - rx_unit: None, - tx: 0, - tx_unit: None, - } - } -} - -fn convert_traffic(traffic: Traffic) -> Option { - let mut t = IfaceTraffic::default(); - // modify traffic values & assign measurement units - // based on received and transmitted values. - // if received > 999 MB, convert it to GB - if traffic.received > 1_047_527_424 { - t.rx = traffic.received / 1_073_741_824; - t.rx_unit = Some("GB".to_string()); - } else if traffic.received > 0 { - // otherwise, convert it to MB - t.rx = (traffic.received / 1024) / 1024; - t.rx_unit = Some("MB".to_string()); - } else { - t.rx = 0; - t.rx_unit = Some("MB".to_string()); - } - - if traffic.transmitted > 1_047_527_424 { - t.tx = traffic.transmitted / 1_073_741_824; - t.tx_unit = Some("GB".to_string()); - } else if traffic.transmitted > 0 { - t.tx = (traffic.transmitted / 1024) / 1024; - t.tx_unit = Some("MB".to_string()); - } else { - t.tx = 0; - t.tx_unit = Some("MB".to_string()); - } - - Some(t) -} - -#[derive(Debug, Serialize)] -pub struct NetworkContext { - pub ap_ip: String, - pub ap_ssid: String, - pub ap_state: String, - pub ap_traffic: Option, - pub wlan_ip: String, - pub wlan_rssi: Option, - pub wlan_ssid: String, - pub wlan_state: String, - pub wlan_status: Option, - pub wlan_traffic: Option, - pub flash_name: Option, - pub flash_msg: Option, - // page title for header in navbar - pub title: Option, - // url for back-arrow link - pub back: Option, -} - -impl NetworkContext { - pub fn build() -> NetworkContext { - let ap_ip = match network::ip("ap0") { - Ok(Some(ip)) => ip, - _ => "x.x.x.x".to_string(), - }; - let ap_ssid = match network::ssid("ap0") { - Ok(Some(ssid)) => ssid, - _ => "Not currently activated".to_string(), - }; - let ap_state = match network::state("ap0") { - Ok(Some(state)) => state, - _ => "Interface unavailable".to_string(), - }; - let ap_traffic = match network::traffic("ap0") { - // convert bytes to mb or gb and add appropriate units - Ok(Some(traffic)) => convert_traffic(traffic), - _ => None, - }; - let wlan_ip = match network::ip("wlan0") { - Ok(Some(ip)) => ip, - _ => "x.x.x.x".to_string(), - }; - let wlan_rssi = match network::rssi_percent("wlan0") { - Ok(rssi) => rssi, - _ => None, - }; - let wlan_ssid = match network::ssid("wlan0") { - Ok(Some(ssid)) => ssid, - _ => "Not connected".to_string(), - }; - let wlan_state = match network::state("wlan0") { - Ok(Some(state)) => state, - _ => "Interface unavailable".to_string(), - }; - let wlan_status = match network::status("wlan0") { - Ok(status) => status, - _ => None, - }; - let wlan_traffic = match network::traffic("wlan0") { - // convert bytes to mb or gb and add appropriate units - Ok(Some(traffic)) => convert_traffic(traffic), - _ => None, - }; - - NetworkContext { - ap_ip, - ap_ssid, - ap_state, - ap_traffic, - wlan_ip, - wlan_rssi, - wlan_ssid, - wlan_state, - wlan_status, - wlan_traffic, - flash_name: None, - flash_msg: None, - title: None, - back: None, - } - } -} - #[get("/network")] pub fn network_status(flash: Option, _auth: Authenticated) -> Template { - // assign context through context_builder call - let mut context = NetworkContext::build(); + let mut context = NetworkStatusContext::build(); context.back = Some("/status".to_string()); context.title = Some("Network Status".to_string()); - // check to see if there is a flash message to display + if let Some(flash) = flash { - // add flash message contents to the context object context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - // template_dir is set in Rocket.toml + Template::render("status/network", &context) } From 65dbc6bdd44f03440ce0f628d3725408ec9ed288 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 14 Jan 2022 15:30:16 +0200 Subject: [PATCH 12/18] remove noscript template snippet --- peach-web/templates/snippets/noscript.html.tera | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 peach-web/templates/snippets/noscript.html.tera diff --git a/peach-web/templates/snippets/noscript.html.tera b/peach-web/templates/snippets/noscript.html.tera deleted file mode 100644 index 5ae7793..0000000 --- a/peach-web/templates/snippets/noscript.html.tera +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file From 6fb4a2406b927f3867f89af7c7fab553d04683ed Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 14 Jan 2022 15:30:33 +0200 Subject: [PATCH 13/18] add docs about standalone mode config --- peach-web/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/peach-web/README.md b/peach-web/README.md index 05b8e3e..ec03092 100644 --- a/peach-web/README.md +++ b/peach-web/README.md @@ -25,7 +25,7 @@ Move into the repo and compile: Run the tests: -`cargo test` +`ROCKET_DISABLE_AUTH=true PEACH_STANDALONE_MODE=false cargo test` Move back to the `peach-workspace` directory: @@ -35,8 +35,6 @@ Run the binary: `./target/release/peach-web` -_Note: Networking functionality requires peach-network microservice to be running._ - ### Environment **Deployment Mode** @@ -47,6 +45,10 @@ The web application deployment mode is configured with the `ROCKET_ENV` environm Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information. +**Configuration Mode** + +The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud). The mode is configured with the `PEACH_STANDALONE_MODE` environment variable: `true` or `false`. If the variable is unset or the value is incorrectly set, the application defaults to standalone mode. + **Authentication** Authentication is disabled in `development` mode and enabled by default when running the application in `production` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`: From 552c4b419e14d560cf2e286d343a7c030e12ad97 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 14 Jan 2022 15:30:54 +0200 Subject: [PATCH 14/18] comment-out system call-invoking function --- peach-web/src/tests.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/peach-web/src/tests.rs b/peach-web/src/tests.rs index d679861..f635360 100644 --- a/peach-web/src/tests.rs +++ b/peach-web/src/tests.rs @@ -325,6 +325,13 @@ fn network_settings_menu_html() { assert!(body.contains("Network Configuration")); } +/* + +NOTE: these tests are commented-out for the moment, due to the fact that they +invoke system commands (resulting in a `sudo` password request during +test execution). see if we can find a way to test the results without +triggering the `systemctl` call. + #[test] fn deploy_ap() { let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); @@ -334,6 +341,16 @@ fn deploy_ap() { assert_eq!(response.content_type(), None); } +#[test] +fn deploy_client() { + let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); + let response = client.get("/settings/network/wifi/activate").dispatch(); + // check for 303 status (redirect) + assert_eq!(response.status(), Status::SeeOther); + assert_eq!(response.content_type(), None); +} +*/ + #[test] fn dns_settings_html() { let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); @@ -370,15 +387,6 @@ fn ap_details_html() { //assert!(body.contains("Network not found")); } -#[test] -fn deploy_client() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/wifi/activate").dispatch(); - // check for 303 status (redirect) - assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.content_type(), None); -} - #[test] fn add_ap_html() { let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); From 6e4b8faf40dc96a6250918de195040a28e02afd9 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 14 Jan 2022 15:31:22 +0200 Subject: [PATCH 15/18] improve error msg --- peach-web/src/routes/settings/network.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/peach-web/src/routes/settings/network.rs b/peach-web/src/routes/settings/network.rs index b913c16..14f0530 100644 --- a/peach-web/src/routes/settings/network.rs +++ b/peach-web/src/routes/settings/network.rs @@ -271,12 +271,9 @@ pub fn add_credentials(wifi: Form, _auth: Authenticated) -> Template { } ("success".to_string(), "Added WiFi credentials".to_string()) } - Err(_) => { + Err(e) => { debug!("Failed to add WiFi credentials."); - ( - "error".to_string(), - "Failed to add WiFi credentials".to_string(), - ) + ("error".to_string(), format!("{}", e)) } } }; From aca687974a1f002508e9f2d4ac022d1e58f12a8c Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 14 Jan 2022 15:31:40 +0200 Subject: [PATCH 16/18] fix url for redirect --- peach-web/src/routes/settings/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs index 8dca817..72ac264 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs @@ -98,7 +98,7 @@ pub fn add_admin(flash: Option, _auth: Authenticated) -> Template #[post("/add", data = "")] pub fn add_admin_post(add_admin_form: Form, _auth: Authenticated) -> Flash { let result = save_add_admin_form(add_admin_form.into_inner()); - let url = uri!(configure_admin); + let url = uri!("/settings/admin/configure"); match result { Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"), Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"), From ed6da528a2574081f5a58b85610780f5fdd1aa81 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 14 Jan 2022 15:32:37 +0200 Subject: [PATCH 17/18] remove noscript, update urls --- peach-web/templates/help.html.tera | 4 ---- peach-web/templates/home.html.tera | 5 ++--- peach-web/templates/scuttlebutt/messages.html.tera | 2 -- .../templates/settings/admin/add_admin.html.tera | 9 ++------- .../settings/admin/change_password.html.tera | 2 +- .../settings/admin/configure_admin.html.tera | 4 ---- .../settings/admin/forgot_password.html.tera | 2 -- peach-web/templates/settings/network/add_ap.html.tera | 2 +- .../settings/network/data_usage_limits.html.tera | 11 ++++++++++- 9 files changed, 16 insertions(+), 25 deletions(-) diff --git a/peach-web/templates/help.html.tera b/peach-web/templates/help.html.tera index 8167f67..4baa227 100644 --- a/peach-web/templates/help.html.tera +++ b/peach-web/templates/help.html.tera @@ -12,10 +12,6 @@
{{ flash_msg }}.
{%- endif %} - - {%- endblock card -%} diff --git a/peach-web/templates/home.html.tera b/peach-web/templates/home.html.tera index 14edce6..23920be 100644 --- a/peach-web/templates/home.html.tera +++ b/peach-web/templates/home.html.tera @@ -24,9 +24,8 @@ - -
-
+
+
diff --git a/peach-web/templates/scuttlebutt/messages.html.tera b/peach-web/templates/scuttlebutt/messages.html.tera index 394f0b0..979a68c 100644 --- a/peach-web/templates/scuttlebutt/messages.html.tera +++ b/peach-web/templates/scuttlebutt/messages.html.tera @@ -5,8 +5,6 @@
{% include "snippets/flash_message" %} - - {% include "snippets/noscript" %}
{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/add_admin.html.tera b/peach-web/templates/settings/admin/add_admin.html.tera index 5c04908..158bad9 100644 --- a/peach-web/templates/settings/admin/add_admin.html.tera +++ b/peach-web/templates/settings/admin/add_admin.html.tera @@ -10,13 +10,8 @@ Cancel - - - {% include "snippets/flash_message" %} - - - {% include "snippets/noscript" %} - + + {% include "snippets/flash_message" %} {%- endblock card -%} diff --git a/peach-web/templates/settings/admin/change_password.html.tera b/peach-web/templates/settings/admin/change_password.html.tera index 65250c7..fe30e82 100644 --- a/peach-web/templates/settings/admin/change_password.html.tera +++ b/peach-web/templates/settings/admin/change_password.html.tera @@ -3,7 +3,7 @@
-
+ diff --git a/peach-web/templates/settings/admin/configure_admin.html.tera b/peach-web/templates/settings/admin/configure_admin.html.tera index 2d3435f..f19cead 100644 --- a/peach-web/templates/settings/admin/configure_admin.html.tera +++ b/peach-web/templates/settings/admin/configure_admin.html.tera @@ -20,11 +20,7 @@ {% endif %} Add Admin
- {% include "snippets/flash_message" %} - - - {% include "snippets/noscript" %}
{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/forgot_password.html.tera b/peach-web/templates/settings/admin/forgot_password.html.tera index 85eff50..f0a254e 100644 --- a/peach-web/templates/settings/admin/forgot_password.html.tera +++ b/peach-web/templates/settings/admin/forgot_password.html.tera @@ -14,7 +14,5 @@ {% include "snippets/flash_message" %} - - {% include "snippets/noscript" %} {%- endblock card -%} diff --git a/peach-web/templates/settings/network/add_ap.html.tera b/peach-web/templates/settings/network/add_ap.html.tera index 440363e..21aa1e1 100644 --- a/peach-web/templates/settings/network/add_ap.html.tera +++ b/peach-web/templates/settings/network/add_ap.html.tera @@ -3,7 +3,7 @@
-
+ diff --git a/peach-web/templates/settings/network/data_usage_limits.html.tera b/peach-web/templates/settings/network/data_usage_limits.html.tera index af93ae1..b36baf2 100644 --- a/peach-web/templates/settings/network/data_usage_limits.html.tera +++ b/peach-web/templates/settings/network/data_usage_limits.html.tera @@ -1,10 +1,19 @@ {%- extends "nav" -%} {%- block card -%} + {# ASSIGN VARIABLES #} + {# ---------------- #} + {%- if data_total -%} + {% set data_usage_total = data_total.total / 1024 / 1024 | round -%} + {%- else -%} + {% set data_usage_total = "x" -%} + {% endif -%}
- +
From 4d2a3771b8038a1cf7d2ea4c4483bbf80258453b Mon Sep 17 00:00:00 2001 From: notplants Date: Sun, 16 Jan 2022 17:41:49 -0500 Subject: [PATCH 18/18] remove static/js from cargo-deb assetts --- peach-web/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 409c82c..ddd4b0d 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -27,7 +27,6 @@ assets = [ ["static/css/*", "/usr/share/peach-web/static/css/", "644"], ["static/icons/*", "/usr/share/peach-web/static/icons/", "644"], ["static/images/*", "/usr/share/peach-web/static/images/", "644"], - ["static/js/*", "/usr/share/peach-web/static/js/", "644"], ["README.md", "/usr/share/doc/peach-web/README", "644"], ]