From 1a7bd7987bc3f076a3e3780df0648dcea816f6c1 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 26 Sep 2022 16:44:22 +0100 Subject: [PATCH 01/16] add network settings menu template and route handler, along with network settings placeholder files for routes --- Cargo.lock | 1 + peach-web/Cargo.toml | 2 +- peach-web/src/private_router.rs | 4 + peach-web/src/routes/settings/mod.rs | 2 +- peach-web/src/routes/settings/network.rs | 322 ------------------ .../src/routes/settings/network/add_ap.rs | 0 .../src/routes/settings/network/ap_details.rs | 0 .../routes/settings/network/configure_dns.rs | 0 .../settings/network/data_usage_limits.rs | 0 .../src/routes/settings/network/list_aps.rs | 0 peach-web/src/routes/settings/network/menu.rs | 57 ++++ peach-web/src/routes/settings/network/mod.rs | 1 + .../src/routes/settings/network/modify_ap.rs | 0 13 files changed, 65 insertions(+), 324 deletions(-) delete mode 100644 peach-web/src/routes/settings/network.rs create mode 100644 peach-web/src/routes/settings/network/add_ap.rs create mode 100644 peach-web/src/routes/settings/network/ap_details.rs create mode 100644 peach-web/src/routes/settings/network/configure_dns.rs create mode 100644 peach-web/src/routes/settings/network/data_usage_limits.rs create mode 100644 peach-web/src/routes/settings/network/list_aps.rs create mode 100644 peach-web/src/routes/settings/network/menu.rs create mode 100644 peach-web/src/routes/settings/network/mod.rs create mode 100644 peach-web/src/routes/settings/network/modify_ap.rs diff --git a/Cargo.lock b/Cargo.lock index f327027..1d97c6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2346,6 +2346,7 @@ dependencies = [ "log 0.4.17", "maud", "peach-lib", + "peach-network", "rouille", "temporary", "xdg", diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 9379556..93605a4 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -45,7 +45,7 @@ log = "0.4" maud = "0.23" peach-lib = { path = "../peach-lib" } # these will be reintroduced when the full peachcloud mode is added -#peach-network = { path = "../peach-network" } +peach-network = { path = "../peach-network" } #peach-stats = { path = "../peach-stats" } rouille = { version = "3.5", default-features = false } temporary = "0.6" diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index 5bc7665..426a690 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -200,6 +200,10 @@ pub fn mount_peachpub_routes( routes::settings::scuttlebutt::default::write_config() }, + (GET) (/settings/network) => { + Response::html(routes::settings::network::menu::build_template(request)).reset_flash() + }, + (GET) (/settings/theme/{theme: String}) => { routes::settings::theme::set_theme(theme) }, diff --git a/peach-web/src/routes/settings/mod.rs b/peach-web/src/routes/settings/mod.rs index d910549..b864d03 100644 --- a/peach-web/src/routes/settings/mod.rs +++ b/peach-web/src/routes/settings/mod.rs @@ -1,6 +1,6 @@ pub mod admin; //pub mod dns; pub mod menu; -//pub mod network; +pub mod network; pub mod scuttlebutt; pub mod theme; diff --git a/peach-web/src/routes/settings/network.rs b/peach-web/src/routes/settings/network.rs deleted file mode 100644 index 0e67c15..0000000 --- a/peach-web/src/routes/settings/network.rs +++ /dev/null @@ -1,322 +0,0 @@ -use log::{debug, warn}; -use rocket::{ - form::{Form, FromForm}, - get, post, - request::FlashMessage, - response::{Flash, Redirect}, - uri, UriDisplayQuery, -}; -use rocket_dyn_templates::{tera::Context, Template}; - -use peach_network::network; - -use crate::{ - context, - context::network::{NetworkAlertContext, NetworkDetailContext, NetworkListContext}, - routes::authentication::Authenticated, - utils::{monitor, monitor::Threshold}, - AP_IFACE, WLAN_IFACE, -}; - -// STRUCTS USED BY NETWORK ROUTES - -#[derive(Debug, FromForm, UriDisplayQuery)] -pub struct Ssid { - pub ssid: String, -} - -#[derive(Debug, FromForm)] -pub struct WiFi { - pub ssid: String, - pub pass: String, -} - -// HELPERS AND ROUTES FOR /settings/network/wifi/usage/reset - -#[get("/wifi/usage/reset")] -pub fn wifi_usage_reset(_auth: Authenticated) -> Flash { - let url = uri!(wifi_usage); - match monitor::reset_data() { - Ok(_) => Flash::success(Redirect::to(url), "Reset stored network traffic total"), - Err(_) => Flash::error( - Redirect::to(url), - "Failed to reset stored network traffic total", - ), - } -} - -#[post("/wifi/connect", data = "")] -pub fn connect_wifi(network: Form, _auth: Authenticated) -> Flash { - let ssid = &network.ssid; - let url = uri!(network_detail(ssid = ssid)); - 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"), - }, - _ => Flash::error(Redirect::to(url), "Failed to retrieve the network ID"), - } -} - -#[post("/wifi/disconnect", data = "")] -pub fn disconnect_wifi(network: Form, _auth: Authenticated) -> Flash { - let ssid = &network.ssid; - let url = uri!(network_home); - 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"), - } -} - -#[post("/wifi/forget", data = "")] -pub fn forget_wifi(network: Form, _auth: Authenticated) -> Flash { - let ssid = &network.ssid; - let url = uri!(network_home); - match network::forget(&*WLAN_IFACE, ssid) { - Ok(_) => Flash::success(Redirect::to(url), "WiFi credentials removed"), - Err(_) => Flash::error( - Redirect::to(url), - "Failed to remove WiFi credentials".to_string(), - ), - } -} - -#[get("/wifi/modify?")] -pub fn wifi_password(ssid: &str, flash: Option, _auth: Authenticated) -> Template { - 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.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/network/modify_ap", &context.into_json()) -} - -#[post("/wifi/modify", data = "")] -pub fn wifi_set_password(wifi: Form, _auth: Authenticated) -> Flash { - let ssid = &wifi.ssid; - let pass = &wifi.pass; - let url = uri!(network_detail(ssid = ssid)); - match network::update(&*WLAN_IFACE, ssid, pass) { - Ok(_) => Flash::success(Redirect::to(url), "WiFi password updated".to_string()), - Err(_) => Flash::error( - Redirect::to(url), - "Failed to update WiFi password".to_string(), - ), - } -} - -// HELPERS AND ROUTES FOR /settings/network - -#[get("/")] -pub fn network_home(flash: Option, _auth: Authenticated) -> Template { - // 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.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.into_json()) -} - -// HELPERS AND ROUTES FOR /settings/network/ap/activate - -#[get("/ap/activate")] -pub fn deploy_ap(_auth: Authenticated) -> Flash { - // activate the wireless access point - debug!("Activating WiFi access point."); - match network::start_iface_service(&*AP_IFACE) { - Ok(_) => Flash::success( - Redirect::to("/settings/network"), - "Activated WiFi access point", - ), - Err(_) => Flash::error( - Redirect::to("/settings/network"), - "Failed to activate WiFi access point", - ), - } -} - -// HELPERS AND ROUTES FOR /settings/network/wifi - -#[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::render("settings/network/list_aps", &context) -} - -// HELPERS AND ROUTES FOR /settings/network/wifi - -#[get("/wifi?")] -pub fn network_detail(ssid: &str, flash: Option, _auth: Authenticated) -> Template { - 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()); - - if let Some(flash) = flash { - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("settings/network/ap_details", &context) -} - -// HELPERS AND ROUTES FOR /settings/network/wifi/activate - -#[get("/wifi/activate")] -pub fn deploy_client(_auth: Authenticated) -> Flash { - // activate the wireless client - debug!("Activating WiFi client mode."); - match network::start_iface_service(&*WLAN_IFACE) { - Ok(_) => Flash::success(Redirect::to("/settings/network"), "Activated WiFi client"), - Err(_) => Flash::error( - Redirect::to("/settings/network"), - "Failed to activate WiFi client", - ), - } -} - -// HELPERS AND ROUTES FOR /settings/network/wifi/add - -#[get("/wifi/add")] -pub fn add_wifi(flash: Option, _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 to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - 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 = 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.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - 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::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 - 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(e) => { - debug!("Failed to add WiFi credentials."); - ("error".to_string(), format!("{}", e)) - } - } - }; - - 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 - -#[get("/wifi/usage")] -pub fn wifi_usage(flash: Option, _auth: Authenticated) -> Template { - let mut context = NetworkAlertContext::build(); - // set back icon link to network route - context.back = Some("/settings/network".to_string()); - context.title = Some("Network Data Usage".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/data_usage_limits", &context) -} - -#[post("/wifi/usage", data = "")] -pub fn wifi_usage_alerts(thresholds: Form, _auth: Authenticated) -> Flash { - match monitor::update_store(thresholds.into_inner()) { - Ok(_) => { - debug!("WiFi data usage thresholds updated."); - Flash::success( - Redirect::to("/settings/network/wifi/usage"), - "Updated alert thresholds and flags", - ) - } - Err(_) => { - warn!("Failed to update WiFi data usage thresholds."); - Flash::error( - Redirect::to("/settings/network/wifi/usage"), - "Failed to update alert thresholds and flags", - ) - } - } -} diff --git a/peach-web/src/routes/settings/network/add_ap.rs b/peach-web/src/routes/settings/network/add_ap.rs new file mode 100644 index 0000000..e69de29 diff --git a/peach-web/src/routes/settings/network/ap_details.rs b/peach-web/src/routes/settings/network/ap_details.rs new file mode 100644 index 0000000..e69de29 diff --git a/peach-web/src/routes/settings/network/configure_dns.rs b/peach-web/src/routes/settings/network/configure_dns.rs new file mode 100644 index 0000000..e69de29 diff --git a/peach-web/src/routes/settings/network/data_usage_limits.rs b/peach-web/src/routes/settings/network/data_usage_limits.rs new file mode 100644 index 0000000..e69de29 diff --git a/peach-web/src/routes/settings/network/list_aps.rs b/peach-web/src/routes/settings/network/list_aps.rs new file mode 100644 index 0000000..e69de29 diff --git a/peach-web/src/routes/settings/network/menu.rs b/peach-web/src/routes/settings/network/menu.rs new file mode 100644 index 0000000..dd9f58a --- /dev/null +++ b/peach-web/src/routes/settings/network/menu.rs @@ -0,0 +1,57 @@ +use maud::{html, Markup, PreEscaped}; +use peach_network::network; +use rouille::Request; + +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; + +// ROUTE: /settings/network + +/// Read the wireless interface mode (WiFi AP or client) and selectively render +/// the activation button for the deactivated mode. +fn render_mode_toggle_button() -> Markup { + match network::state("ap0") { + Ok(Some(state)) if state == "up" => { + html! { + a id="connectWifi" class="button button-primary center" href="/settings/network/wifi/activate" title="Enable WiFi" { "Enable WiFi" } + } + } + _ => html! { + a id="deployAccessPoint" class="button button-primary center" href="/settings/network/ap/activate" title="Deploy Access Point" { "Deploy Access Point" } + }, + } +} + +/// Network settings menu template builder. +pub fn build_template(request: &Request) -> PreEscaped { + let (flash_name, flash_msg) = request.retrieve_flash(); + + let menu_template = html! { + (PreEscaped("")) + div class="card center" { + (PreEscaped("")) + div id="buttons" { + a class="button button-primary center" href="/settings/network/wifi/add" title="Add WiFi Network" { "Add WiFi Network" } + a id="configureDNS" class="button button-primary center" href="/settings/network/dns" title="Configure DNS" { "Configure DNS" } + (PreEscaped("")) + (render_mode_toggle_button()) + a id="listWifi" class="button button-primary center" href="/settings/network/wifi" title="List WiFi Networks" { "List WiFi Networks" } + a id="viewUsage" class="button button-primary center" href="/settings/network/wifi/usage" title="View Data Usage" { "View Data Usage" } + a id="viewStatus" class="button button-primary center" href="/status/network" title="View Network Status" { "View Network Status" } + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + }; + + let body = templates::nav::build_template(menu_template, "Network Settings", Some("/settings")); + + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) +} diff --git a/peach-web/src/routes/settings/network/mod.rs b/peach-web/src/routes/settings/network/mod.rs new file mode 100644 index 0000000..b9a0e3e --- /dev/null +++ b/peach-web/src/routes/settings/network/mod.rs @@ -0,0 +1 @@ +pub mod menu; diff --git a/peach-web/src/routes/settings/network/modify_ap.rs b/peach-web/src/routes/settings/network/modify_ap.rs new file mode 100644 index 0000000..e69de29 From b6cd54142cedda0c20b7ece2a592453ba8f608f3 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 29 Sep 2022 14:26:56 +0100 Subject: [PATCH 02/16] add template builder and form handler for modifying wifi ap password --- peach-web/src/routes/settings/network/mod.rs | 1 + .../src/routes/settings/network/modify_ap.rs | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/peach-web/src/routes/settings/network/mod.rs b/peach-web/src/routes/settings/network/mod.rs index b9a0e3e..36b4d6a 100644 --- a/peach-web/src/routes/settings/network/mod.rs +++ b/peach-web/src/routes/settings/network/mod.rs @@ -1 +1,2 @@ pub mod menu; +pub mod modify_ap; diff --git a/peach-web/src/routes/settings/network/modify_ap.rs b/peach-web/src/routes/settings/network/modify_ap.rs index e69de29..8e7b6fd 100644 --- a/peach-web/src/routes/settings/network/modify_ap.rs +++ b/peach-web/src/routes/settings/network/modify_ap.rs @@ -0,0 +1,104 @@ +use maud::{html, Markup, PreEscaped}; +use peach_network::network; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{ + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + theme, + }, +}; + +// ROUTE: /settings/network/wifi/modify? + +fn render_ssid_input(selected_ap: Option) -> Markup { + html! { + (PreEscaped("")) + input id="ssid" name="ssid" class="center input" type="text" placeholder="SSID" title="Network name (SSID) for WiFi access point" value=[selected_ap] autofocus; + } +} + +fn render_password_input() -> Markup { + html! { + (PreEscaped("")) + input id="pass" name="pass" class="center input" type="password" placeholder="Password" title="Password for WiFi access point"; + } +} + +fn render_buttons() -> Markup { + html! { + (PreEscaped("")) + div id="buttons" { + input id="savePassword" class="button button-primary center" title="Save" type="submit" value="Save"; + a class="button button-secondary center" href="/settings/network" title="Cancel" { "Cancel" } + } + } +} + +/// WiFi access point password modification form template builder. +pub fn build_template(request: &Request, selected_ap: Option) -> PreEscaped { + let (flash_name, flash_msg) = request.retrieve_flash(); + + let form_template = html! { + (PreEscaped("")) + div class="card center" { + form id="wifiModify" action="/settings/network/wifi/modify" method="post" { + (render_ssid_input(selected_ap)) + (render_password_input()) + (render_buttons()) + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + }; + + let body = templates::nav::build_template( + form_template, + "Change WiFi Password", + Some("/settings/network"), + ); + + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) +} + +/// Parse the SSID and password for an access point and save the new password. +pub fn handle_form(request: &Request) -> Response { + let data = try_or_400!(post_input!(request, { + ssid: String, + pass: String, + })); + + let (name, msg) = match network::id("wlan0", &data.ssid) { + Ok(Some(id)) => match network::modify(&id, &data.ssid, &data.pass) { + Ok(_) => ("success".to_string(), "WiFi password updated".to_string()), + Err(err) => ( + "error".to_string(), + format!("Failed to update WiFi password: {}", err), + ), + }, + Ok(None) => ( + "error".to_string(), + format!( + "Failed to update WiFi password: no saved credentials found for network {}", + &data.ssid + ), + ), + Err(err) => ( + "error".to_string(), + format!( + "Failed to update WiFi password: no ID found for network {}: {}", + &data.ssid, err + ), + ), + }; + + let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); + + Response::redirect_303("/settings/network/wifi/modify").add_flash(flash_name, flash_msg) +} From 97030fbfbf8341cfd82129c85b4e5f20a69d7a1b Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 29 Sep 2022 14:27:46 +0100 Subject: [PATCH 03/16] mount GET and POST routes for modifying wifi ap password --- peach-web/src/private_router.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index 426a690..79dfe30 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -204,6 +204,18 @@ pub fn mount_peachpub_routes( Response::html(routes::settings::network::menu::build_template(request)).reset_flash() }, + (GET) (/settings/network/wifi/modify) => { + Response::html(routes::settings::network::modify_ap::build_template(request, None)).reset_flash() + }, + + (POST) (/settings/network/wifi/modify) => { + routes::settings::network::modify_ap::handle_form(request) + }, + + (GET) (/settings/network/wifi/modify/{ssid: String}) => { + Response::html(routes::settings::network::modify_ap::build_template(request, Some(ssid))).reset_flash() + }, + (GET) (/settings/theme/{theme: String}) => { routes::settings::theme::set_theme(theme) }, From 61ef909ed303fc049075630cc8463f166df04bf7 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 30 Sep 2022 15:33:36 +0100 Subject: [PATCH 04/16] add dns configuration template builder and form handler --- .../routes/settings/network/configure_dns.rs | 201 ++++++++++++++++++ peach-web/src/routes/settings/network/mod.rs | 1 + 2 files changed, 202 insertions(+) diff --git a/peach-web/src/routes/settings/network/configure_dns.rs b/peach-web/src/routes/settings/network/configure_dns.rs index e69de29..a619234 100644 --- a/peach-web/src/routes/settings/network/configure_dns.rs +++ b/peach-web/src/routes/settings/network/configure_dns.rs @@ -0,0 +1,201 @@ +use log::info; +use maud::{html, Markup, PreEscaped}; +use peach_lib::{ + config_manager, dyndns_client, + error::PeachError, + jsonrpc_client_core::{Error, ErrorKind}, + jsonrpc_core::types::error::ErrorCode, +}; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{ + error::PeachWebError, + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + theme, + }, +}; + +// ROUTE: /settings/network/dns + +fn render_dyndns_status_indicator() -> Markup { + let (indicator_class, indicator_label) = match dyndns_client::is_dns_updater_online() { + Ok(true) => ("success-border", "Dynamic DNS is currently online."), + _ => ( + "warning-border", + "Dynamic DNS is enabled but may be offline.", + ), + }; + + html! { + (PreEscaped("")) + div id="dyndns-status-indicator" class={ "stack capsule " (indicator_class) } { + div class="stack" { + label class="label-small font-near-black" { (indicator_label) } + } + } + } +} + +fn render_external_domain_input() -> Markup { + let external_domain = config_manager::get_config_value("EXTERNAL_DOMAIN").ok(); + + html! { + div class="input-wrapper" { + (PreEscaped("")) + label id="external_domain" class="label-small input-label font-near-black" { + label class="label-small input-label font-gray" for="external_domain" style="padding-top: 0.25rem;" { "External Domain (optional)" } + input id="external_domain" class="form-input" style="margin-bottom: 0;" name="external_domain" type="text" title="external domain" value=[external_domain]; + } + } + } +} + +fn render_dyndns_enabled_checkbox() -> Markup { + let dyndns_enabled = config_manager::get_dyndns_enabled_value().unwrap_or(false); + + html! { + div class="input-wrapper" { + div { + (PreEscaped("")) + label class="label-small input-label font-gray" { "Enable Dynamic DNS" } + input style="margin-left: 0px;" id="enable_dyndns" name="enable_dyndns" title="Activate dynamic DNS" type="checkbox" checked[dyndns_enabled]; + } + } + } +} + +fn render_dynamic_domain_input() -> Markup { + let dyndns_domain = + config_manager::get_config_value("DYN_DOMAIN").unwrap_or_else(|_| String::from("")); + let dyndns_subdomain = + dyndns_client::get_dyndns_subdomain(&dyndns_domain).unwrap_or(dyndns_domain); + + html! { + div class="input-wrapper" { + (PreEscaped("")) + label id="cut" class="label-small input-label font-near-black" { + label class="label-small input-label font-gray" for="cut" style="padding-top: 0.25rem;" { "Dynamic DNS Domain" } + input id="dyndns_domain" class="alert-input" name="dynamic_domain" placeholder="" type="text" title="dyndns_domain" value=(dyndns_subdomain); + { ".dyn.peachcloud.org" } + } + } + } +} + +fn render_save_button() -> Markup { + html! { + div id="buttonDiv" style="margin-top: 2rem;" { + input id="configureDNSButton" class="button button-primary center" title="Add" type="submit" value="Save"; + } + } +} + +/// DNS configuration form template builder. +pub fn build_template(request: &Request) -> PreEscaped { + let (flash_name, flash_msg) = request.retrieve_flash(); + + let dyndns_enabled = config_manager::get_dyndns_enabled_value().unwrap_or(false); + + let form_template = html! { + (PreEscaped("")) + div class="card center" { + @if dyndns_enabled { + (render_dyndns_status_indicator()) + } + form id="configureDNS" class="center" action="/settings/network/dns" method="post" { + (render_external_domain_input()) + (render_dyndns_enabled_checkbox()) + (render_dynamic_domain_input()) + (render_save_button()) + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + } + }; + + let body = templates::nav::build_template( + form_template, + "Configure Dynamic DNS", + Some("/settings/network"), + ); + + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) +} + +pub fn save_dns_configuration( + external_domain: String, + enable_dyndns: bool, + dynamic_domain: String, +) -> Result<(), PeachWebError> { + // first save local configurations + config_manager::set_external_domain(&external_domain)?; + config_manager::set_dyndns_enabled_value(enable_dyndns)?; + + let full_dynamic_domain = dyndns_client::get_full_dynamic_domain(&dynamic_domain); + + // if dynamic dns is enabled and this is a new domain name, then register it + if enable_dyndns && dyndns_client::check_is_new_dyndns_domain(&full_dynamic_domain)? { + if let Err(registration_err) = dyndns_client::register_domain(&full_dynamic_domain) { + info!("Failed to register dyndns domain: {:?}", registration_err); + + // error message describing the failed update + let err_msg = match registration_err { + PeachError::JsonRpcClientCore(Error(ErrorKind::JsonRpcError(rpc_err), _)) => { + if let ErrorCode::ServerError(-32030) = rpc_err.code { + format!( + "Error registering domain: {} was previously registered", + full_dynamic_domain + ) + } else { + format!("Failed to register dyndns domain: {:?}", rpc_err) + } + } + _ => "Failed to register dyndns domain".to_string(), + }; + + Err(PeachWebError::FailedToRegisterDynDomain(err_msg)) + } else { + info!("Registered new dyndns domain"); + + Ok(()) + } + } else { + info!("Domain {} already registered", dynamic_domain); + + Ok(()) + } +} + +/// Parse the DNS configuration parameters and apply them. +pub fn handle_form(request: &Request) -> Response { + let data = try_or_400!(post_input!(request, { + external_domain: String, + enable_dyndns: bool, + dynamic_domain: String, + })); + + let (name, msg) = match save_dns_configuration( + data.external_domain, + data.enable_dyndns, + data.dynamic_domain, + ) { + Ok(_) => ( + "success".to_string(), + "New dynamic DNS configuration is now enabled".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!("Failed to save DNS configuration: {}", err), + ), + }; + + let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); + + Response::redirect_303("/settings/network/dns").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/settings/network/mod.rs b/peach-web/src/routes/settings/network/mod.rs index 36b4d6a..e9bf79e 100644 --- a/peach-web/src/routes/settings/network/mod.rs +++ b/peach-web/src/routes/settings/network/mod.rs @@ -1,2 +1,3 @@ +pub mod configure_dns; pub mod menu; pub mod modify_ap; From acab30acceb9985a5b350f919ed440d6975bf01e Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 30 Sep 2022 15:34:19 +0100 Subject: [PATCH 05/16] mount GET and POST routes for dns configuration --- peach-web/src/private_router.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index 79dfe30..7bb7804 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -204,6 +204,14 @@ pub fn mount_peachpub_routes( Response::html(routes::settings::network::menu::build_template(request)).reset_flash() }, + (GET) (/settings/network/dns) => { + Response::html(routes::settings::network::configure_dns::build_template(request)).reset_flash() + }, + + (POST) (/settings/network/dns) => { + routes::settings::network::configure_dns::handle_form(request) + }, + (GET) (/settings/network/wifi/modify) => { Response::html(routes::settings::network::modify_ap::build_template(request, None)).reset_flash() }, From 4f36f61128e753e84aa50dbfe8019ab4c8240e37 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 3 Oct 2022 10:48:25 +0100 Subject: [PATCH 06/16] add refactored template for ap list --- .../src/routes/settings/network/list_aps.rs | 103 ++++++++++++++++++ peach-web/src/routes/settings/network/mod.rs | 1 + 2 files changed, 104 insertions(+) diff --git a/peach-web/src/routes/settings/network/list_aps.rs b/peach-web/src/routes/settings/network/list_aps.rs index e69de29..4eaeb37 100644 --- a/peach-web/src/routes/settings/network/list_aps.rs +++ b/peach-web/src/routes/settings/network/list_aps.rs @@ -0,0 +1,103 @@ +use std::collections::HashMap; + +use maud::{html, Markup, PreEscaped}; +use peach_network::{network, AccessPoint}; +use rouille::Request; + +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; + +// ROUTE: /settings/network/wifi + +/// Retrieve network state data required by the WiFi network list template. +fn get_network_state_data(ap: &str, wlan: &str) -> (String, String, HashMap) { + let ap_state = match network::state("ap0") { + Ok(Some(state)) => state, + Err(_) => "Interface unavailable".to_string(), + }; + + let wlan_ssid = match network::ssid("wlan0") { + Ok(Some(ssid)) => ssid, + Err(_) => "Not connected".to_string(), + }; + + let wlan_networks = match network::all_networks("wlan0") { + Ok(networks) => networks, + Err(_) => HashMap::new(), + }; + + (ap_state, wlan_ssid, wlan_networks) +} + +fn render_network_connected_elements(ssid: String) -> Markup { + let ap_detail_url = format!("/network/wifi?ssid={}", ssid); + + html! { + a class="list-item link primary-bg" href=(ap_detail_url) { + img id="netStatus" class="icon icon-active icon-medium list-icon" src="/icons/wifi.svg" alt="WiFi online"; + p class="list-text" { (ssid) } + label class="label-small list-label font-gray" for="netStatus" title="Status" { "Connected" } + } + } +} + +fn render_network_available_elements(ssid: String, ap_state: String) -> Markup { + let ap_detail_url = format!("/network/wifi?ssid={}", ssid); + + html! { + a class="list-item link light-bg" href=(ap_detail_url) { + img id="netStatus" class="icon icon-inactive icon-medium list-icon" src="/icons/wifi.svg" alt="WiFi offline"; + p class="list-text" { (ssid) } + label class="label-small list-label font-gray" for="netStatus" title="Status" { (ap_state) } + } + } +} + +fn render_network_unavailable_elements(ssid: String, ap_state: String) -> Markup { + let ap_detail_url = format!("/network/wifi?ssid={}", ssid); + + html! { + a class="list-item link" href=(ap_detail_url) { + img id="netStatus" class="icon icon-inactive icon-medium list-icon" src="/icons/wifi.svg" alt="WiFi offline"; + p class="list-text" { (ssid) } + label class="label-small list-label font-gray" for="netStatus" title="Status" { (ap_state) } + } + } +} + +/// WiFi network list template builder. +pub fn build_template(request: &Request) -> PreEscaped { + let (ap_state, wlan_ssid, wlan_networks) = get_network_state_data("ap0", "wlan0"); + + html! { + div class="card center" { + div class="center list-container" { + ul class="list" { + @if ap_state == "up" { + li class="list-item light-bg warning-border" { + "Enable WiFi client mode to view saved and available networks." + } + } @else if wlan_networks { + @for (ssid, ap) in wlan_networks { + li { + @if ssid == wlan_ssid { + (render_network_connected_elements(ssid)) + } @else if ap.state == "Available" { + (render_network_available_elements(ssid, ap.state)) + } @else { + (render_network_unavailable_elements(ssid, ap.state)) + } + } + } + } @else { + li class="list-item light-bg" { + "No saved or available networks found." + } + } + } + } + } + } +} diff --git a/peach-web/src/routes/settings/network/mod.rs b/peach-web/src/routes/settings/network/mod.rs index e9bf79e..ecba5d8 100644 --- a/peach-web/src/routes/settings/network/mod.rs +++ b/peach-web/src/routes/settings/network/mod.rs @@ -1,3 +1,4 @@ pub mod configure_dns; +pub mod list_aps; pub mod menu; pub mod modify_ap; From bdd3b7ab9b2dd7380760a4fd0540e663ba2e5a58 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 3 Oct 2022 10:48:56 +0100 Subject: [PATCH 07/16] add wip refactored template for ap detail --- .../src/routes/settings/network/ap_details.rs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/peach-web/src/routes/settings/network/ap_details.rs b/peach-web/src/routes/settings/network/ap_details.rs index e69de29..d6e5c82 100644 --- a/peach-web/src/routes/settings/network/ap_details.rs +++ b/peach-web/src/routes/settings/network/ap_details.rs @@ -0,0 +1,120 @@ +use maud::{html, PreEscaped}; +use peach_network::{network, network::Scan}; +use rouille::Request; + +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; + +// ROUTE: /settings/network/wifi? + +fn render_status_elements<'a>() -> () { + let wlan_ssid = match network::ssid("wlan0") { + Ok(Some(ssid)) => ssid, + _ => String::from("Not connected") + }; + + if + div class="two-grid capsule{% if ssid == wlan_ssid %} success-border{% endif %}" title="PeachCloud network mode and status" { + +} + +fn render_network_status_icon() { + + + (PreEscaped("")) + + div class="grid-column-1" { + img id="wifiIcon" class="center icon" src="/icons/wifi.svg" alt="WiFi icon"; + label class="center label-small font-gray" for="wifiIcon" title="Access Point Status">{% if ssid == wlan_ssid %}CONNECTED{% elif ap.state == "Available" %}AVAILABLE{% else %}NOT IN RANGE{% endif %}; + } +} + +fn render_network_detailed_info() -> Markup { + html! { + (PreEscaped("")) + div class="grid-column-2" { + label class="label-small font-gray" for="netSsid" title="WiFi network SSID" { "SSID" }; + p id="netSsid" class="card-text" title="SSID" { (ssid) } + label class="label-small font-gray" for="netSec" title="Security protocol" { "SECURITY" }; + p id="netSec" class="card-text" title="Security protocol in use by {{ ssid }}" { "{% if ap.detail %}{% if ap.detail.protocol != "" %}{{ ap.detail.protocol }}{% else %}None{% endif %}{% else %}Unknown{% endif %}" } + label class="label-small font-gray" for="netSig" title="Signal Strength" { "SIGNAL" }; + p id="netSig" class="card-text" title="Signal strength of WiFi access point" { "{% if ap.signal %}{{ ap.signal }}%{% else %}Unknown{% endif %}" } + } + } +} + +// fn render_network_card +// +fn render_buttons() -> Markup { + html! { + (PreEscaped("")) + div class="card-container" style="padding-top: 0;" { + div id="buttonDiv" { + {%- if wlan_ssid == selected -%} + form id="wifiDisconnect" action="/settings/network/wifi/disconnect" method="post" { + (PreEscaped("")) + input id="disconnectSsid" name="ssid" type="text" value="{{ ssid }}" style="display: none;"; + input id="disconnectWifi" class="button button-warning center" title="Disconnect from Network" type="submit" value="Disconnect"; + } + {%- endif -%} + {%- if saved_aps -%} + {# Loop through the list of AP's with saved credentials #} + {%- for ap in saved_aps -%} + {# If the selected access point appears in the list, #} + {# display the Modify and Forget buttons. #} + {%- if ap.ssid == selected -%} + {# Set 'in_list' to true to allow correct Add button display #} + {% set_global in_list = true %} + {%- if wlan_ssid != selected and ap.state == "Available" -%} + form id="wifiConnect" action="/settings/network/wifi/connect" method="post" { + (PreEscaped("")) + input id="connectSsid" name="ssid" type="text" value="{{ ap.ssid }}" style="display: none;"; + input id="connectWifi" class="button button-primary center" title="Connect to Network" type="submit" value="Connect"; + } + {%- endif -%} + a class="button button-primary center" href="/settings/network/wifi/modify?ssid={{ ssid }}" { "Modify" } + form id="wifiForget" action="/settings/network/wifi/forget" method="post" { + (PreEscaped("")) + input id="forgetSsid" name="ssid" type="text" value="{{ ap.ssid }}" style="display: none;"; + input id="forgetWifi" class="button button-warning center" title="Forget Network" type="submit" value="Forget"; + } + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- if in_list == false -%} + {# Display the Add button if AP creds not already in saved networks list #} + a class="button button-primary center" href="/settings/network/wifi/add?ssid={{ ssid }}" { "Add" } + {%- endif -%} + a class="button button-secondary center" href="/settings/network/wifi" title="Cancel" { "Cancel" }/ + +/// WiFi access point (AP) details. +pub fn build_template(request: &Request, selected_ap: String) -> PreEscaped { + let network_list_template = html! { + @if let Ok(Some(wlan_networks)) = network::all_networks("wlan0") { + @for ssid, ap in wlan_networks { + // select only the access point we are interested in displaying + @if ssid == selected_ap { + (PreEscaped("")) + div class="card center" { + (PreEscaped("")) + div class="two-grid capsule{% if ssid == wlan_ssid %} success-border{% endif %}" title="PeachCloud network mode and status" { + (PreEscaped("")) + // network status icon goes here + // ... + // network detailed info goes here + // ... + } + // buttons go here + // ... + + } + // flash + } + } + } + } + } + } +} From 4fb4ea2f9c59bd7cdb8d41a39d2246699744a529 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 3 Oct 2022 11:39:58 +0100 Subject: [PATCH 08/16] merge latest lockfile --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1d97c6e..659ff50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2293,7 +2293,7 @@ dependencies = [ [[package]] name = "peach-network" -version = "0.4.2" +version = "0.5.0" dependencies = [ "get_if_addrs", "miniserde", From 0814eedf13e4c3327ea4901d131cf3b1600c3d99 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 3 Oct 2022 11:40:26 +0100 Subject: [PATCH 09/16] add ap list template and mount route --- peach-web/src/private_router.rs | 4 ++ .../src/routes/settings/network/list_aps.rs | 48 ++++++++++--------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index 7bb7804..f3b398d 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -212,6 +212,10 @@ pub fn mount_peachpub_routes( routes::settings::network::configure_dns::handle_form(request) }, + (GET) (/settings/network/wifi) => { + Response::html(routes::settings::network::list_aps::build_template()) + }, + (GET) (/settings/network/wifi/modify) => { Response::html(routes::settings::network::modify_ap::build_template(request, None)).reset_flash() }, diff --git a/peach-web/src/routes/settings/network/list_aps.rs b/peach-web/src/routes/settings/network/list_aps.rs index 4eaeb37..7af9206 100644 --- a/peach-web/src/routes/settings/network/list_aps.rs +++ b/peach-web/src/routes/settings/network/list_aps.rs @@ -1,34 +1,30 @@ use std::collections::HashMap; use maud::{html, Markup, PreEscaped}; -use peach_network::{network, AccessPoint}; -use rouille::Request; +use peach_network::{network, network::AccessPoint}; -use crate::{ - templates, - utils::{flash::FlashRequest, theme}, -}; +use crate::{templates, utils::theme}; // ROUTE: /settings/network/wifi /// Retrieve network state data required by the WiFi network list template. fn get_network_state_data(ap: &str, wlan: &str) -> (String, String, HashMap) { - let ap_state = match network::state("ap0") { + let ap_state = match network::state(ap) { Ok(Some(state)) => state, - Err(_) => "Interface unavailable".to_string(), + _ => "Interface unavailable".to_string(), }; - let wlan_ssid = match network::ssid("wlan0") { + let wlan_ssid = match network::ssid(wlan) { Ok(Some(ssid)) => ssid, - Err(_) => "Not connected".to_string(), + _ => "Not connected".to_string(), }; - let wlan_networks = match network::all_networks("wlan0") { + let network_list = match network::all_networks(wlan) { Ok(networks) => networks, Err(_) => HashMap::new(), }; - (ap_state, wlan_ssid, wlan_networks) + (ap_state, wlan_ssid, network_list) } fn render_network_connected_elements(ssid: String) -> Markup { @@ -68,10 +64,10 @@ fn render_network_unavailable_elements(ssid: String, ap_state: String) -> Markup } /// WiFi network list template builder. -pub fn build_template(request: &Request) -> PreEscaped { - let (ap_state, wlan_ssid, wlan_networks) = get_network_state_data("ap0", "wlan0"); +pub fn build_template() -> PreEscaped { + let (ap_state, wlan_ssid, network_list) = get_network_state_data("ap0", "wlan0"); - html! { + let list_template = html! { div class="card center" { div class="center list-container" { ul class="list" { @@ -79,8 +75,12 @@ pub fn build_template(request: &Request) -> PreEscaped { li class="list-item light-bg warning-border" { "Enable WiFi client mode to view saved and available networks." } - } @else if wlan_networks { - @for (ssid, ap) in wlan_networks { + } @else if network_list.is_empty() { + li class="list-item light-bg" { + "No saved or available networks found." + } + } @else { + @for (ssid, ap) in network_list { li { @if ssid == wlan_ssid { (render_network_connected_elements(ssid)) @@ -91,13 +91,17 @@ pub fn build_template(request: &Request) -> PreEscaped { } } } - } @else { - li class="list-item light-bg" { - "No saved or available networks found." - } + } } } } - } + }; + + let body = + templates::nav::build_template(list_template, "WiFi Networks", Some("/settings/network")); + + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) } From fedf2855ede9b054f0f2018195cf6fe71a59937b Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 10 Oct 2022 09:17:54 +0100 Subject: [PATCH 10/16] add data usage template module but leave it commented out for now --- peach-web/Cargo.toml | 2 + .../settings/network/data_usage_limits.rs | 163 ++++++++++++++++++ peach-web/src/routes/settings/network/menu.rs | 27 +-- peach-web/src/routes/settings/network/mod.rs | 2 + 4 files changed, 184 insertions(+), 10 deletions(-) diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 93605a4..c3a34ab 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -49,4 +49,6 @@ peach-network = { path = "../peach-network" } #peach-stats = { path = "../peach-stats" } rouille = { version = "3.5", default-features = false } temporary = "0.6" +# TODO: uncomment this when data usage feature is in place +#vnstat_parse = "0.1.0" xdg = "2.2" diff --git a/peach-web/src/routes/settings/network/data_usage_limits.rs b/peach-web/src/routes/settings/network/data_usage_limits.rs index e69de29..efa5038 100644 --- a/peach-web/src/routes/settings/network/data_usage_limits.rs +++ b/peach-web/src/routes/settings/network/data_usage_limits.rs @@ -0,0 +1,163 @@ +// TODO: +// +// This template and associated feature set requires vnstat_parse. +// - https://crates.io/crates/vnstat_parse +// +// Use the PeachCloud config system to store warning and cutoff flags, +// as well as the associated totals (thresholds): +// +// - DATA_WARNING_ENABLED +// - DATA_WARNING_LIMIT +// - DATA_CUTOFF_ENABLED +// - DATA_CUTOFF_LIMIT + +use maud::{html, Markup, PreEscaped}; +use peach_network::network; +use rouille::Request; +use vnstat_parse::Vnstat; + +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; + +// ROUTE: /settings/network/wifi/usage + +fn render_data_usage_total_capsule() -> Markup { + html! { + div class="stack capsule" style="margin-left: 2rem; margin-right: 2rem;" { + div class="flex-grid" { + label id="dataTotal" class="label-large" title="Data download total in MB" { + data_total.total / 1024 / 1024 | round + } + label class="label-small font-near-black" { "MB" } + } + label class="center-text label-small font-gray" { "USAGE TOTAL" } + } + } +} + +fn render_warning_threshold_icon() -> Markup { + // threshold.warn_flag + let warning_enabled = true; + + let icon_class = match warning_enabled { + true => "icon", + false => "icon icon-inactive", + }; + + html! { + div class="card-container container" { + div { + img id="warnIcon" class=(icon_class) alt="Warning" title="Warning threshold" src="/icons/alert.svg"; + } + } + } +} + +fn render_warning_threshold_input() -> Markup { + // TODO: source threshold.warn value and replace below + + html! { + div { + (PreEscaped("")) + label id="warn" class="label-small font-near-black" { + input id="warnInput" class="alert-input" name="warn" placeholder="0" type="text" title="Warning threshold value" value="{{ threshold.warn }}" { "MB" } + } + label class="label-small font-gray" for="warn" style="padding-top: 0.25rem;" { "WARNING THRESHOLD" } + } + } +} + +fn render_warning_threshold_checkbox() -> Markup { + let warning_enabled = true; + + html! { + div { + (PreEscaped("")) + input id="warnCheck" name="warn_flag" title="Activate warning" type="checkbox" checked[warning_enabled]; + } + } +} + +fn render_critical_threshold_icon() -> Markup { + // threshold.cut_flag + let cutoff_enabled = true; + + let icon_class = match cutoff_enabled { + true => "icon", + false => "icon icon-inactive", + }; + + html! { + div { + img id="cutIcon" + class=(icon_class) + alt="Cutoff" + title="Cutoff threshold" + src="/icons/scissor.svg"; + } + } +} + +fn render_critical_threshold_input() -> Markup { + // TODO: source threshold.cut value and replace below + + html! { + div { + (PreEscaped("")) + label id="cut" class="label-small font-near-black"> Markup { + // threshold.cut_flag + let cutoff_enabled = true; + + html! { + div { + (PreEscaped("")) + input id="cutCheck" name="cut_flag" title="Activate cutoff" type="checkbox" checked[cutoff_enabled]; + } + } +} + +fn render_buttons() -> Markup { + html! { + div id="buttonDiv" class="button-div" { + input id="updateAlerts" class="button button-primary center" title="Update" type="submit" value="Update"; + a id="resetTotal" class="button button-warning center" href="/settings/network/wifi/usage/reset" title="Reset stored usage total to zero" { "Reset" } + a class="button button-secondary center" href="/settings/network" title="Cancel" { "Cancel" } + } + } +} + +/// WiFi data usage form template builder. +pub fn build_template(request: &Request) -> PreEscaped { + let (flash_name, flash_msg) = request.retrieve_flash(); + + let wlan_data = Vnstat::get("wlan0"); + + // wlan_data.all_time_total + // wlan_data.all_time_total_unit + + let form_template = html! { + (PreEscaped("")) + form id="wifiAlerts" action="/network/wifi/usage" class="card center" method="post" { + (render_data_usage_total_capsule()) + (render_warning_threshold_icon()) + (render_warning_threshold_input()) + (render_warning_threshold_checkbox()) + (render_critical_threshold_icon()) + (render_critical_threshold_input()) + (render_critical_threshold_checkbox()) + (render_buttons()) + } + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + }; +} diff --git a/peach-web/src/routes/settings/network/menu.rs b/peach-web/src/routes/settings/network/menu.rs index dd9f58a..e4ae934 100644 --- a/peach-web/src/routes/settings/network/menu.rs +++ b/peach-web/src/routes/settings/network/menu.rs @@ -24,6 +24,22 @@ fn render_mode_toggle_button() -> Markup { } } +fn render_buttons() -> Markup { + html! { + (PreEscaped("")) + div id="buttons" { + a class="button button-primary center" href="/settings/network/wifi/add" title="Add WiFi Network" { "Add WiFi Network" } + a id="configureDNS" class="button button-primary center" href="/settings/network/dns" title="Configure DNS" { "Configure DNS" } + (PreEscaped("")) + (render_mode_toggle_button()) + a id="listWifi" class="button button-primary center" href="/settings/network/wifi" title="List WiFi Networks" { "List WiFi Networks" } + // TODO: uncomment this once data usage feature is in place + // a id="viewUsage" class="button button-primary center" href="/settings/network/wifi/usage" title="View Data Usage" { "View Data Usage" } + a id="viewStatus" class="button button-primary center" href="/status/network" title="View Network Status" { "View Network Status" } + } + } +} + /// Network settings menu template builder. pub fn build_template(request: &Request) -> PreEscaped { let (flash_name, flash_msg) = request.retrieve_flash(); @@ -31,16 +47,7 @@ pub fn build_template(request: &Request) -> PreEscaped { let menu_template = html! { (PreEscaped("")) div class="card center" { - (PreEscaped("")) - div id="buttons" { - a class="button button-primary center" href="/settings/network/wifi/add" title="Add WiFi Network" { "Add WiFi Network" } - a id="configureDNS" class="button button-primary center" href="/settings/network/dns" title="Configure DNS" { "Configure DNS" } - (PreEscaped("")) - (render_mode_toggle_button()) - a id="listWifi" class="button button-primary center" href="/settings/network/wifi" title="List WiFi Networks" { "List WiFi Networks" } - a id="viewUsage" class="button button-primary center" href="/settings/network/wifi/usage" title="View Data Usage" { "View Data Usage" } - a id="viewStatus" class="button button-primary center" href="/status/network" title="View Network Status" { "View Network Status" } - } + (render_buttons()) // render flash message if cookies were found in the request @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { (PreEscaped("")) diff --git a/peach-web/src/routes/settings/network/mod.rs b/peach-web/src/routes/settings/network/mod.rs index ecba5d8..0c0979d 100644 --- a/peach-web/src/routes/settings/network/mod.rs +++ b/peach-web/src/routes/settings/network/mod.rs @@ -1,4 +1,6 @@ pub mod configure_dns; +// TODO: uncomment this once data usage feature is in place +// pub mod data_usage_limits; pub mod list_aps; pub mod menu; pub mod modify_ap; From 24deb4601a0e6a58f79fbaafa6ba896503d6d366 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 10 Oct 2022 09:18:20 +0100 Subject: [PATCH 11/16] update lockfile --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 659ff50..99e1f25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2408,7 +2408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f77e66f6d6d898cbbd4a09c48fd3507cfc210b7c83055de02a38b5f7a1e6d216" dependencies = [ "libc", - "time 0.1.44", + "time 0.3.11", ] [[package]] From 8cd8ee5dd6be7b6e16d3cecc039f773f549dca69 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 10 Oct 2022 10:39:04 +0100 Subject: [PATCH 12/16] mount routes for adding wifi ap credentials --- peach-web/src/private_router.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index f3b398d..90a89e1 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -216,6 +216,18 @@ pub fn mount_peachpub_routes( Response::html(routes::settings::network::list_aps::build_template()) }, + (GET) (/settings/network/wifi/add) => { + Response::html(routes::settings::network::add_ap::build_template(request, None)).reset_flash() + }, + + (POST) (/settings/network/wifi/add) => { + routes::settings::network::add_ap::handle_form(request) + }, + + (GET) (/settings/network/wifi/add/{ssid: String}) => { + Response::html(routes::settings::network::add_ap::build_template(request, Some(ssid))).reset_flash() + }, + (GET) (/settings/network/wifi/modify) => { Response::html(routes::settings::network::modify_ap::build_template(request, None)).reset_flash() }, From e91c40355a1a107ae12a197b49ff7dd88bb8e5b9 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 10 Oct 2022 10:39:29 +0100 Subject: [PATCH 13/16] add template builder and form handler for adding wifi ap --- .../src/routes/settings/network/add_ap.rs | 96 +++++++++++++++++++ peach-web/src/routes/settings/network/mod.rs | 1 + 2 files changed, 97 insertions(+) diff --git a/peach-web/src/routes/settings/network/add_ap.rs b/peach-web/src/routes/settings/network/add_ap.rs index e69de29..1efc972 100644 --- a/peach-web/src/routes/settings/network/add_ap.rs +++ b/peach-web/src/routes/settings/network/add_ap.rs @@ -0,0 +1,96 @@ +use maud::{html, Markup, PreEscaped}; +use peach_network::network; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{ + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + theme, + }, +}; + +// ROUTE: /settings/network/wifi/add + +fn render_ssid_input(selected_ap: Option) -> Markup { + html! { + (PreEscaped("")) + input id="ssid" name="ssid" class="center input" type="text" placeholder="SSID" title="Network name (SSID) for WiFi access point" value=[selected_ap] autofocus; + } +} + +fn render_password_input() -> Markup { + html! { + (PreEscaped("")) + input id="pass" name="pass" class="center input" type="password" placeholder="Password" title="Password for WiFi access point"; + } +} + +fn render_buttons() -> Markup { + html! { + (PreEscaped("")) + div id="buttons" { + input id="addWifi" class="button button-primary center" title="Add" type="submit" value="Add"; + a class="button button-secondary center" href="/settings/network" title="Cancel" { "Cancel" } + } + } +} + +/// WiFi access point credentials form template builder. +pub fn build_template(request: &Request, selected_ap: Option) -> PreEscaped { + let (flash_name, flash_msg) = request.retrieve_flash(); + + let form_template = html! { + (PreEscaped("")) + div class="card center" { + form id="wifiCreds" action="/settings/network/wifi/add" method="post" { + (render_ssid_input(selected_ap)) + (render_password_input()) + (render_buttons()) + } + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + }; + + let body = templates::nav::build_template( + form_template, + "Add WiFi Network", + Some("/settings/network"), + ); + + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) +} + +/// Parse the SSID and password for an access point and save the new credentials. +pub fn handle_form(request: &Request) -> Response { + let data = try_or_400!(post_input!(request, { + ssid: String, + pass: String, + })); + + let (name, msg) = match network::add("wlan0", &data.ssid, &data.pass) { + Ok(_) => match network::reconfigure() { + Ok(_) => ("success".to_string(), "Added WiFi credentials".to_string()), + Err(err) => ( + "error".to_string(), + format!( + "Added WiFi credentials but failed to reconfigure interface: {}", + err + ), + ), + }, + Err(err) => ( + "error".to_string(), + format!("Failed to add WiFi credentials for {}: {}", &data.ssid, err), + ), + }; + + let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); + + Response::redirect_303("/settings/network/wifi/add").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/settings/network/mod.rs b/peach-web/src/routes/settings/network/mod.rs index 0c0979d..a55a2f0 100644 --- a/peach-web/src/routes/settings/network/mod.rs +++ b/peach-web/src/routes/settings/network/mod.rs @@ -1,3 +1,4 @@ +pub mod add_ap; pub mod configure_dns; // TODO: uncomment this once data usage feature is in place // pub mod data_usage_limits; From 8b0381ead1d17882de70c7f415422dc1c53b4484 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 18 Oct 2022 12:00:40 +0100 Subject: [PATCH 14/16] fix template indentation --- peach-web/src/routes/settings/network/list_aps.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peach-web/src/routes/settings/network/list_aps.rs b/peach-web/src/routes/settings/network/list_aps.rs index 7af9206..52ef853 100644 --- a/peach-web/src/routes/settings/network/list_aps.rs +++ b/peach-web/src/routes/settings/network/list_aps.rs @@ -32,9 +32,9 @@ fn render_network_connected_elements(ssid: String) -> Markup { html! { a class="list-item link primary-bg" href=(ap_detail_url) { - img id="netStatus" class="icon icon-active icon-medium list-icon" src="/icons/wifi.svg" alt="WiFi online"; - p class="list-text" { (ssid) } - label class="label-small list-label font-gray" for="netStatus" title="Status" { "Connected" } + img id="netStatus" class="icon icon-active icon-medium list-icon" src="/icons/wifi.svg" alt="WiFi online"; + p class="list-text" { (ssid) } + label class="label-small list-label font-gray" for="netStatus" title="Status" { "Connected" } } } } From 3eab3e3687911be068872f3bf7653971b12a738d Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 18 Oct 2022 12:01:28 +0100 Subject: [PATCH 15/16] add and mount ap detail template --- peach-web/src/private_router.rs | 4 + .../src/routes/settings/network/ap_details.rs | 254 ++++++++++++------ peach-web/src/routes/settings/network/mod.rs | 1 + 3 files changed, 170 insertions(+), 89 deletions(-) diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index 90a89e1..248e375 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -240,6 +240,10 @@ pub fn mount_peachpub_routes( Response::html(routes::settings::network::modify_ap::build_template(request, Some(ssid))).reset_flash() }, + (GET) (/settings/network/wifi/{ssid: String}) => { + Response::html(routes::settings::network::ap_details::build_template(request, ssid)) + }, + (GET) (/settings/theme/{theme: String}) => { routes::settings::theme::set_theme(theme) }, diff --git a/peach-web/src/routes/settings/network/ap_details.rs b/peach-web/src/routes/settings/network/ap_details.rs index d6e5c82..6a67847 100644 --- a/peach-web/src/routes/settings/network/ap_details.rs +++ b/peach-web/src/routes/settings/network/ap_details.rs @@ -1,5 +1,7 @@ -use maud::{html, PreEscaped}; -use peach_network::{network, network::Scan}; +use std::collections::HashMap; + +use maud::{html, Markup, PreEscaped}; +use peach_network::{network, network::AccessPoint, NetworkError}; use rouille::Request; use crate::{ @@ -9,112 +11,186 @@ use crate::{ // ROUTE: /settings/network/wifi? -fn render_status_elements<'a>() -> () { - let wlan_ssid = match network::ssid("wlan0") { - Ok(Some(ssid)) => ssid, - _ => String::from("Not connected") +fn render_network_status_icon(ssid: &str, wlan_ssid: &str, ap_state: &str) -> Markup { + let status_label_value = if ssid == wlan_ssid { + "CONNECTED" + } else if ap_state == "Available" { + "AVAILABLE" + } else { + "NOT IN RANGE" }; - - if - div class="two-grid capsule{% if ssid == wlan_ssid %} success-border{% endif %}" title="PeachCloud network mode and status" { + html! { + (PreEscaped("")) + div class="grid-column-1" { + img id="wifiIcon" class="center icon" src="/icons/wifi.svg" alt="WiFi icon"; + label class="center label-small font-gray" for="wifiIcon" title="Access Point Status" { (status_label_value) } + } + } } -fn render_network_status_icon() { - +fn render_network_detailed_info(ssid: &str, ap_protocol: &str, ap_signal: Option) -> Markup { + let ap_signal_value = match ap_signal { + Some(signal) => signal.to_string(), + None => "Unknown".to_string(), + }; - (PreEscaped("")) - - div class="grid-column-1" { - img id="wifiIcon" class="center icon" src="/icons/wifi.svg" alt="WiFi icon"; - label class="center label-small font-gray" for="wifiIcon" title="Access Point Status">{% if ssid == wlan_ssid %}CONNECTED{% elif ap.state == "Available" %}AVAILABLE{% else %}NOT IN RANGE{% endif %}; - } -} - -fn render_network_detailed_info() -> Markup { html! { (PreEscaped("")) div class="grid-column-2" { label class="label-small font-gray" for="netSsid" title="WiFi network SSID" { "SSID" }; p id="netSsid" class="card-text" title="SSID" { (ssid) } label class="label-small font-gray" for="netSec" title="Security protocol" { "SECURITY" }; - p id="netSec" class="card-text" title="Security protocol in use by {{ ssid }}" { "{% if ap.detail %}{% if ap.detail.protocol != "" %}{{ ap.detail.protocol }}{% else %}None{% endif %}{% else %}Unknown{% endif %}" } + p id="netSec" class="card-text" title={ "Security protocol in use by " (ssid) } { (ap_protocol) } label class="label-small font-gray" for="netSig" title="Signal Strength" { "SIGNAL" }; - p id="netSig" class="card-text" title="Signal strength of WiFi access point" { "{% if ap.signal %}{{ ap.signal }}%{% else %}Unknown{% endif %}" } + p id="netSig" class="card-text" title="Signal strength of WiFi access point" { (ap_signal_value) } } } } -// fn render_network_card -// -fn render_buttons() -> Markup { +fn render_disconnect_form(ssid: &str) -> Markup { + html! { + form id="wifiDisconnect" action="/settings/network/wifi/disconnect" method="post" { + (PreEscaped("")) + input id="disconnectSsid" name="ssid" type="text" value=(ssid) style="display: none;"; + input id="disconnectWifi" class="button button-warning center" title="Disconnect from Network" type="submit" value="Disconnect"; + } + } +} + +fn render_connect_form(ssid: &str) -> Markup { + html! { + form id="wifiConnect" action="/settings/network/wifi/connect" method="post" { + (PreEscaped("")) + input id="connectSsid" name="ssid" type="text" value=(ssid) style="display: none;"; + input id="connectWifi" class="button button-primary center" title="Connect to Network" type="submit" value="Connect"; + } + } +} + +fn render_forget_form(ssid: &str) -> Markup { + html! { + form id="wifiForget" action="/settings/network/wifi/forget" method="post" { + (PreEscaped("")) + input id="forgetSsid" name="ssid" type="text" value=(ssid) style="display: none;"; + input id="forgetWifi" class="button button-warning center" title="Forget Network" type="submit" value="Forget"; + } + } +} + +fn render_buttons( + selected_ap: &str, + wlan_ssid: &str, + ap: &AccessPoint, + saved_wifi_networks: Vec, +) -> Markup { html! { (PreEscaped("")) - div class="card-container" style="padding-top: 0;" { - div id="buttonDiv" { - {%- if wlan_ssid == selected -%} - form id="wifiDisconnect" action="/settings/network/wifi/disconnect" method="post" { - (PreEscaped("")) - input id="disconnectSsid" name="ssid" type="text" value="{{ ssid }}" style="display: none;"; - input id="disconnectWifi" class="button button-warning center" title="Disconnect from Network" type="submit" value="Disconnect"; - } - {%- endif -%} - {%- if saved_aps -%} - {# Loop through the list of AP's with saved credentials #} - {%- for ap in saved_aps -%} - {# If the selected access point appears in the list, #} - {# display the Modify and Forget buttons. #} - {%- if ap.ssid == selected -%} - {# Set 'in_list' to true to allow correct Add button display #} - {% set_global in_list = true %} - {%- if wlan_ssid != selected and ap.state == "Available" -%} - form id="wifiConnect" action="/settings/network/wifi/connect" method="post" { - (PreEscaped("")) - input id="connectSsid" name="ssid" type="text" value="{{ ap.ssid }}" style="display: none;"; - input id="connectWifi" class="button button-primary center" title="Connect to Network" type="submit" value="Connect"; - } - {%- endif -%} - a class="button button-primary center" href="/settings/network/wifi/modify?ssid={{ ssid }}" { "Modify" } - form id="wifiForget" action="/settings/network/wifi/forget" method="post" { - (PreEscaped("")) - input id="forgetSsid" name="ssid" type="text" value="{{ ap.ssid }}" style="display: none;"; - input id="forgetWifi" class="button button-warning center" title="Forget Network" type="submit" value="Forget"; - } - {%- endif -%} - {%- endfor -%} - {%- endif -%} - {%- if in_list == false -%} - {# Display the Add button if AP creds not already in saved networks list #} - a class="button button-primary center" href="/settings/network/wifi/add?ssid={{ ssid }}" { "Add" } - {%- endif -%} - a class="button button-secondary center" href="/settings/network/wifi" title="Cancel" { "Cancel" }/ - -/// WiFi access point (AP) details. -pub fn build_template(request: &Request, selected_ap: String) -> PreEscaped { - let network_list_template = html! { - @if let Ok(Some(wlan_networks)) = network::all_networks("wlan0") { - @for ssid, ap in wlan_networks { - // select only the access point we are interested in displaying - @if ssid == selected_ap { - (PreEscaped("")) - div class="card center" { - (PreEscaped("")) - div class="two-grid capsule{% if ssid == wlan_ssid %} success-border{% endif %}" title="PeachCloud network mode and status" { - (PreEscaped("")) - // network status icon goes here - // ... - // network detailed info goes here - // ... - } - // buttons go here - // ... - - } - // flash - } - } - } + div id="buttons" { + @if wlan_ssid == selected_ap { + (render_disconnect_form(selected_ap)) } + @if saved_wifi_networks.contains(&selected_ap.to_string()) { + @if wlan_ssid != selected_ap && ap.state == "Available" { + (render_connect_form(selected_ap)) + } + a class="button button-primary center" href={ "/settings/network/wifi/modify?ssid=" (selected_ap) } { "Modify" } + (render_forget_form(selected_ap)) + } @else { + // display the Add button if AP creds not already in saved + // networks list + a class="button button-primary center" href={ "/settings/network/wifi/add?ssid=" (selected_ap) } { "Add" } + } + a class="button button-secondary center" href="/settings/network/wifi" title="Cancel" { "Cancel" } } } } + +/// Retrieve the list of all saved and in-range networks (including SSID and +/// AP details for each network), the list of all saved networks (SSIDs only) +/// and the SSID for the WiFi interface. +fn retrieve_network_data() -> ( + Result, NetworkError>, + Vec, + String, +) { + let all_wifi_networks = network::all_networks("wlan0"); + let saved_wifi_networks = match network::saved_networks() { + Ok(Some(ssids)) => ssids, + _ => Vec::new(), + }; + let wlan_ssid = match network::ssid("wlan0") { + Ok(Some(ssid)) => ssid, + _ => String::from("Not connected"), + }; + + (all_wifi_networks, saved_wifi_networks, wlan_ssid) +} + +/// WiFi access point (AP) template builder. +/// +/// Render a UI card with details about the selected access point, including +/// the connection state, security protocol being used, the SSID and the +/// signal strength. Buttons are also rendering based on the state of the +/// access point and whether or not credentials for the AP have previously +/// been saved. +/// +/// If the AP is available (ie. in-range) then a Connect button is rendered. +/// A Disconnect button is rendered if the WiFi client is currently +/// connected to the AP. +/// +/// If credentials have not previously been saved for the AP, an Add button is +/// rendered. Forget and Modify buttons are rendered if credentials for the AP +/// have previously been saved. +pub fn build_template(request: &Request, selected_ap: String) -> PreEscaped { + let (flash_name, flash_msg) = request.retrieve_flash(); + + let (all_wifi_networks, saved_wifi_networks, wlan_ssid) = retrieve_network_data(); + + let network_info_box_class = if selected_ap == wlan_ssid { + "two-grid capsule success-border" + } else { + "two-grid capsule" + }; + + let network_list_template = html! { + (PreEscaped("")) + div class="card center" { + @if let Ok(wlan_networks) = all_wifi_networks { + // select only the access point we are interested in displaying + @if let Some((ssid, ap)) = wlan_networks.get_key_value(&selected_ap) { + @let ap_protocol = match &ap.detail { + Some(detail) => detail.protocol.clone(), + None => "None".to_string() + }; + (PreEscaped("")) + div class=(network_info_box_class) title="PeachCloud network mode and status" { + (PreEscaped("")) + (render_network_status_icon(ssid, &wlan_ssid, &ap.state)) + (PreEscaped("")) + (render_network_detailed_info(ssid, &ap_protocol, ap.signal)) + } + (render_buttons(ssid, &wlan_ssid, ap, saved_wifi_networks)) + } @else { + p class="card-text list-item" { (selected_ap) " not found in saved or in-range networks" } + } + } @else { + p class="card-text list-item" { "No saved or in-range networks found" } + } + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + }; + + let body = templates::nav::build_template( + network_list_template, + "WiFi Networks", + Some("/settings/network"), + ); + + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) +} diff --git a/peach-web/src/routes/settings/network/mod.rs b/peach-web/src/routes/settings/network/mod.rs index a55a2f0..666bc39 100644 --- a/peach-web/src/routes/settings/network/mod.rs +++ b/peach-web/src/routes/settings/network/mod.rs @@ -1,4 +1,5 @@ pub mod add_ap; +pub mod ap_details; pub mod configure_dns; // TODO: uncomment this once data usage feature is in place // pub mod data_usage_limits; From 1e7a54b728489fc35ac782574231ae9ff5546c75 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 18 Oct 2022 12:06:36 +0100 Subject: [PATCH 16/16] remove blank line in template --- peach-web/src/routes/settings/network/list_aps.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/peach-web/src/routes/settings/network/list_aps.rs b/peach-web/src/routes/settings/network/list_aps.rs index 52ef853..9a78b0f 100644 --- a/peach-web/src/routes/settings/network/list_aps.rs +++ b/peach-web/src/routes/settings/network/list_aps.rs @@ -91,7 +91,6 @@ pub fn build_template() -> PreEscaped { } } } - } } }