Merge pull request 'Wide range of web improvements' (#70) from web_improvements into main
Reviewed-on: #70
This commit is contained in:
commit
5d75aebf0d
|
@ -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"],
|
||||
]
|
||||
|
||||
|
@ -37,18 +36,15 @@ maintenance = { status = "actively-developed" }
|
|||
|
||||
[dependencies]
|
||||
env_logger = "0.8"
|
||||
lazy_static = "1.4.0"
|
||||
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"
|
||||
snafu = "0.6"
|
||||
tera = { version = "1.12.1", features = ["builtins"] }
|
||||
xdg = "2.2.0"
|
||||
|
||||
|
|
|
@ -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`:
|
||||
|
|
|
@ -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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub mod dns;
|
||||
pub mod network;
|
|
@ -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<Scan>,
|
||||
pub signal: Option<i32>,
|
||||
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<IfaceTraffic> {
|
||||
// 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<String>,
|
||||
pub data_total: Option<Data>, // combined stored and current wifi traffic in bytes
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub threshold: Threshold,
|
||||
pub title: Option<String>,
|
||||
pub traffic: Option<IfaceTraffic>, // 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<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub selected: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub saved_aps: Vec<String>,
|
||||
pub wlan_ip: String,
|
||||
pub wlan_networks: HashMap<String, AccessPoint>,
|
||||
pub wlan_rssi: Option<String>,
|
||||
pub wlan_ssid: String,
|
||||
pub wlan_state: String,
|
||||
pub wlan_status: Option<Status>,
|
||||
pub wlan_traffic: Option<IfaceTraffic>,
|
||||
}
|
||||
|
||||
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::<i32>().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<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub wlan_networks: HashMap<String, String>,
|
||||
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<IfaceTraffic>,
|
||||
pub wlan_ip: String,
|
||||
pub wlan_rssi: Option<String>,
|
||||
pub wlan_ssid: String,
|
||||
pub wlan_state: String,
|
||||
pub wlan_status: Option<Status>,
|
||||
pub wlan_traffic: Option<IfaceTraffic>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
// passing in the ssid of a chosen access point
|
||||
pub selected: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub back: Option<String>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<serde_json::error::Error> 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<serde_yaml::Error> 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<JsonError> for PeachWebError {
|
||||
fn from(err: JsonError) -> PeachWebError {
|
||||
PeachWebError::Json(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<YamlError> for PeachWebError {
|
||||
fn from(err: YamlError) -> PeachWebError {
|
||||
PeachWebError::Yaml(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PeachError> for PeachWebError {
|
||||
fn from(err: PeachError) -> PeachWebError {
|
||||
PeachWebError::PeachLibError {
|
||||
PeachWebError::PeachLib {
|
||||
source: err,
|
||||
msg: "".to_string(),
|
||||
}
|
||||
|
|
|
@ -24,107 +24,41 @@
|
|||
|
||||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
mod context;
|
||||
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<dyn std::error::Error>;
|
||||
|
||||
/// Create rocket instance & mount all routes.
|
||||
fn init_rocket() -> Rocket<Build> {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
static WLAN_IFACE: &str = "wlan0";
|
||||
static AP_IFACE: &str = "ap0";
|
||||
|
||||
pub fn init_rocket() -> Rocket<Build> {
|
||||
info!("Initializing Rocket");
|
||||
if *STANDALONE_MODE {
|
||||
router::build_minimal_rocket()
|
||||
} else {
|
||||
router::build_complete_rocket()
|
||||
}
|
||||
}
|
||||
|
||||
/// Launch the peach-web rocket server.
|
||||
|
@ -134,7 +68,6 @@ async fn main() {
|
|||
env_logger::init();
|
||||
|
||||
// initialize rocket
|
||||
info!("Initializing Rocket");
|
||||
let rocket = init_rocket();
|
||||
|
||||
// launch rocket
|
||||
|
|
|
@ -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<Build> {
|
||||
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<Build> {
|
||||
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())
|
||||
}
|
|
@ -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<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl LoginContext {
|
||||
pub fn build() -> LoginContext {
|
||||
LoginContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/login")]
|
||||
pub fn login(flash: Option<FlashMessage>) -> 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 = "<login_form>")]
|
||||
pub fn login_post(login_form: Form<LoginForm>, 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<LoginForm>, 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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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<FlashMessage>) -> 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 = "<reset_password_form>")]
|
||||
pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> 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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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<FlashMessage>) -> 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<FlashMessage>) -> 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<FlashMessage>, _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 = "<password_form>")]
|
||||
pub fn change_password_post(password_form: Form<PasswordForm>, _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())
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl HelpContext {
|
||||
pub fn build() -> HelpContext {
|
||||
HelpContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/help")]
|
||||
pub fn help(flash: Option<FlashMessage>) -> 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("help", &context.into_json())
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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<FlashMessage>, _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<String>,
|
||||
pub back: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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<FlashMessage>, _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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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,23 +81,24 @@ pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError
|
|||
|
||||
#[get("/add")]
|
||||
pub fn add_admin(flash: Option<FlashMessage>, _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 = "<add_admin_form>")]
|
||||
pub fn add_admin_post(add_admin_form: Form<AddAdminForm>, _auth: Authenticated) -> Flash<Redirect> {
|
||||
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"),
|
||||
|
|
|
@ -3,23 +3,20 @@ use rocket::{
|
|||
form::{Form, FromForm},
|
||||
get, post,
|
||||
request::FlashMessage,
|
||||
serde::{Deserialize, Serialize},
|
||||
serde::Deserialize,
|
||||
};
|
||||
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;
|
||||
use crate::{
|
||||
context::dns::ConfigureDNSContext, error::PeachWebError, routes::authentication::Authenticated,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct DnsForm {
|
||||
|
@ -32,11 +29,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,24 +46,22 @@ 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(),
|
||||
};
|
||||
Err(PeachWebError::FailedToRegisterDynDomain { msg })
|
||||
Err(PeachWebError::FailedToRegisterDynDomain(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,74 +74,44 @@ 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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
impl ConfigureDNSContext {
|
||||
pub fn build() -> ConfigureDNSContext {
|
||||
let peach_config = load_peach_config().unwrap();
|
||||
let dyndns_fulldomain = peach_config.dyn_domain;
|
||||
let is_dyndns_online = is_dns_updater_online().unwrap();
|
||||
let dyndns_subdomain =
|
||||
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<FlashMessage>, _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)
|
||||
}
|
||||
|
||||
#[post("/dns", data = "<dns>")]
|
||||
pub fn configure_dns_post(dns: Form<DnsForm>, _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)
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
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<FlashMessage>, _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())
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
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 std::collections::HashMap;
|
||||
use rocket_dyn_templates::{tera::Context, Template};
|
||||
|
||||
use peach_lib::network_client;
|
||||
use peach_lib::network_client::{AccessPoint, Networks, Scan};
|
||||
use peach_lib::stats_client::Traffic;
|
||||
use peach_network::network;
|
||||
|
||||
use crate::routes::authentication::Authenticated;
|
||||
use crate::utils::monitor;
|
||||
use crate::utils::monitor::{Alert, Data, Threshold};
|
||||
use crate::{
|
||||
context,
|
||||
context::network::{NetworkAlertContext, NetworkDetailContext, NetworkListContext},
|
||||
routes::authentication::Authenticated,
|
||||
utils::{monitor, monitor::Threshold},
|
||||
AP_IFACE, WLAN_IFACE,
|
||||
};
|
||||
|
||||
// STRUCTS USED BY NETWORK ROUTES
|
||||
|
||||
|
@ -50,12 +50,12 @@ pub fn wifi_usage_reset(_auth: Authenticated) -> Flash<Redirect> {
|
|||
pub fn connect_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ pub fn connect_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect
|
|||
pub fn disconnect_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect> {
|
||||
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"),
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ pub fn disconnect_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redir
|
|||
pub fn forget_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect> {
|
||||
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),
|
||||
|
@ -84,21 +84,19 @@ pub fn forget_wifi(network: Form<Ssid>, _auth: Authenticated) -> Flash<Redirect>
|
|||
|
||||
#[get("/wifi/modify?<ssid>")]
|
||||
pub fn wifi_password(ssid: &str, flash: Option<FlashMessage>, _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 = "<wifi>")]
|
||||
|
@ -106,7 +104,7 @@ pub fn wifi_set_password(wifi: Form<WiFi>, _auth: Authenticated) -> Flash<Redire
|
|||
let ssid = &wifi.ssid;
|
||||
let pass = &wifi.pass;
|
||||
let url = uri!(network_detail(ssid = ssid));
|
||||
match network_client::update("wlan0", ssid, pass) {
|
||||
match network::update(&*WLAN_IFACE, ssid, pass) {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "WiFi password updated".to_string()),
|
||||
Err(_) => Flash::error(
|
||||
Redirect::to(url),
|
||||
|
@ -117,174 +115,23 @@ pub fn wifi_set_password(wifi: Form<WiFi>, _auth: Authenticated) -> Flash<Redire
|
|||
|
||||
// HELPERS AND ROUTES FOR /settings/network
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkContext {
|
||||
pub ap_ip: String,
|
||||
pub ap_ssid: String,
|
||||
pub ap_state: String,
|
||||
pub ap_traffic: Option<Traffic>,
|
||||
pub wlan_ip: String,
|
||||
pub wlan_rssi: Option<String>,
|
||||
pub wlan_scan: Option<Vec<Scan>>,
|
||||
pub wlan_ssid: String,
|
||||
pub wlan_state: String,
|
||||
pub wlan_status: String,
|
||||
pub wlan_traffic: Option<Traffic>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
// allows for passing in the ssid of a chosen access point
|
||||
// this is used in the network_detail template
|
||||
pub selected: Option<String>,
|
||||
// page title for header in navbar
|
||||
pub title: Option<String>,
|
||||
// url for back-arrow link
|
||||
pub back: Option<String>,
|
||||
}
|
||||
|
||||
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<Scan> = 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<FlashMessage>, _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
|
||||
|
@ -293,7 +140,7 @@ pub fn network_home(flash: Option<FlashMessage>, _auth: Authenticated) -> Templa
|
|||
pub fn deploy_ap(_auth: Authenticated) -> Flash<Redirect> {
|
||||
// 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",
|
||||
|
@ -307,252 +154,37 @@ pub fn deploy_ap(_auth: Authenticated) -> Flash<Redirect> {
|
|||
|
||||
// HELPERS AND ROUTES FOR /settings/network/wifi
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkListContext {
|
||||
pub ap_state: String,
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub wlan_networks: HashMap<String, String>,
|
||||
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<Networks> = 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<Networks> = 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<FlashMessage>, _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<ssid>
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkDetailContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub saved_aps: Vec<Networks>,
|
||||
pub selected: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub wlan_ip: String,
|
||||
pub wlan_networks: HashMap<String, AccessPoint>,
|
||||
pub wlan_rssi: Option<String>,
|
||||
pub wlan_ssid: String,
|
||||
pub wlan_state: String,
|
||||
pub wlan_status: String,
|
||||
pub wlan_traffic: Option<Traffic>,
|
||||
}
|
||||
|
||||
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<Networks> = 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<Networks> instead
|
||||
let saved_aps = match network_client::saved_networks() {
|
||||
Ok(ssids) => {
|
||||
let networks: Vec<Networks> = 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<Scan> = 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::<i32>().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?<ssid>")]
|
||||
pub fn network_detail(ssid: &str, flash: Option<FlashMessage>, _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)
|
||||
}
|
||||
|
||||
|
@ -562,7 +194,7 @@ pub fn network_detail(ssid: &str, flash: Option<FlashMessage>, _auth: Authentica
|
|||
pub fn deploy_client(_auth: Authenticated) -> Flash<Redirect> {
|
||||
// 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"),
|
||||
|
@ -575,152 +207,85 @@ pub fn deploy_client(_auth: Authenticated) -> Flash<Redirect> {
|
|||
|
||||
#[get("/wifi/add")]
|
||||
pub fn add_wifi(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||
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?<ssid>
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkAddContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub selected: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
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?<ssid>")]
|
||||
pub fn add_ssid(ssid: &str, flash: Option<FlashMessage>, _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 = "<wifi>")]
|
||||
pub fn add_credentials(wifi: Form<WiFi>, _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(e) => {
|
||||
debug!("Failed to add WiFi credentials.");
|
||||
("error".to_string(), format!("{}", e))
|
||||
}
|
||||
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<String>,
|
||||
pub data_total: Data, // combined stored and current wifi traffic in bytes
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub threshold: Threshold,
|
||||
pub title: Option<String>,
|
||||
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<FlashMessage>, _auth: Authenticated) -> Template {
|
||||
let mut context = NetworkAlertContext::build();
|
||||
|
|
|
@ -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<String>,
|
||||
pub tx: u64,
|
||||
pub tx_unit: Option<String>,
|
||||
}
|
||||
|
||||
impl IfaceTraffic {
|
||||
fn default() -> Self {
|
||||
IfaceTraffic {
|
||||
rx: 0,
|
||||
rx_unit: None,
|
||||
tx: 0,
|
||||
tx_unit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_traffic(traffic: Traffic) -> Option<IfaceTraffic> {
|
||||
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<IfaceTraffic>,
|
||||
pub wlan_ip: String,
|
||||
pub wlan_rssi: Option<String>,
|
||||
pub wlan_ssid: String,
|
||||
pub wlan_state: String,
|
||||
pub wlan_status: Option<Status>,
|
||||
pub wlan_traffic: Option<IfaceTraffic>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
// page title for header in navbar
|
||||
pub title: Option<String>,
|
||||
// url for back-arrow link
|
||||
pub back: Option<String>,
|
||||
}
|
||||
|
||||
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<FlashMessage>, _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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -328,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");
|
||||
|
@ -337,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");
|
||||
|
@ -373,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");
|
||||
|
@ -509,179 +514,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]
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -12,10 +12,6 @@
|
|||
<!-- display error message -->
|
||||
<div class="center-text flash-message font-failure" style="padding-left: 5px;">{{ flash_msg }}.</div>
|
||||
{%- endif %}
|
||||
<!-- share ux information with the user if JS is disabled -->
|
||||
<noscript>
|
||||
<p class="center-text flash-message">This website will be unresponsive while the device shuts down or reboots.</p>
|
||||
</noscript>
|
||||
</div>
|
||||
</div>
|
||||
{%- endblock card -%}
|
||||
|
|
|
@ -24,9 +24,8 @@
|
|||
</div>
|
||||
</a>
|
||||
<!-- middle -->
|
||||
<a class="middle" href="/hello">
|
||||
<div class="circle circle-large">
|
||||
</div>
|
||||
<a class="middle">
|
||||
<div class="circle circle-large"></div>
|
||||
</a>
|
||||
<!-- bottom-left -->
|
||||
<!-- SYSTEM STATUS LINK AND ICON -->
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<div class="card-container">
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
||||
{% include "snippets/noscript" %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endblock card -%}
|
||||
|
|
|
@ -10,13 +10,8 @@
|
|||
<a class="button button-secondary center" href="/settings/admin/configure" title="Cancel">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
|
||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
||||
{% include "snippets/noscript" %}
|
||||
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endblock card -%}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<!-- CHANGE PASSWORD FORM -->
|
||||
<div class="card center">
|
||||
<div class="form-container">
|
||||
<form id="changePassword" action="/settings/change_password" method="post">
|
||||
<form id="changePassword" action="/settings/admin/change_password" method="post">
|
||||
<!-- input for current password -->
|
||||
<input id="currentPassword" class="center input" name="current_password" type="password" placeholder="Current password" title="Current password" autofocus>
|
||||
<!-- input for new password -->
|
||||
|
|
|
@ -20,11 +20,7 @@
|
|||
{% endif %}
|
||||
<a class="button button-primary center full-width" style="margin-top: 25px;" href="/settings/admin/add" title="Add Admin">Add Admin</a>
|
||||
</div>
|
||||
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
|
||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
||||
{% include "snippets/noscript" %}
|
||||
</div>
|
||||
{%- endblock card -%}
|
||||
|
|
|
@ -14,7 +14,5 @@
|
|||
</form>
|
||||
<!-- FLASH MESSAGE -->
|
||||
{% include "snippets/flash_message" %}
|
||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
||||
{% include "snippets/noscript" %}
|
||||
</div>
|
||||
{%- endblock card -%}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<!-- NETWORK ADD CREDENTIALS FORM -->
|
||||
<div class="card center">
|
||||
<div class="card-container">
|
||||
<form id="wifiCreds" action="/network/wifi/add" method="post">
|
||||
<form id="wifiCreds" action="/settings/network/wifi/add" method="post">
|
||||
<!-- input for network ssid -->
|
||||
<input id="ssid" name="ssid" class="center input" type="text" placeholder="SSID" title="Network name (SSID) for WiFi access point" value="{%- if selected -%}{{ selected }}{%- endif -%}" autofocus>
|
||||
<!-- input for network password -->
|
||||
|
|
|
@ -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 -%}
|
||||
<!-- NETWORK DATA ALERTS VIEW -->
|
||||
<form id="wifiAlerts" action="/settings/network/wifi/usage" class="card center" method="post">
|
||||
<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>
|
||||
<label id="dataTotal" class="label-large" title="Data download total in MB">
|
||||
{{ data_usage_total }}
|
||||
</label>
|
||||
<label class="label-small font-near-black">MB</label>
|
||||
</div>
|
||||
<label class="center-text label-small font-gray">USAGE TOTAL</label>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<!-- share ux information with the user if JS is disabled -->
|
||||
<noscript>
|
||||
<div class="capsule flash-message info-border">
|
||||
<p class="center-text">This website may be temporarily unresponsive while settings are being saved.</p>
|
||||
</div>
|
||||
</noscript>
|
Loading…
Reference in New Issue