Compare commits
56 Commits
fix-regres
...
ssb_status
Author | SHA1 | Date | |
---|---|---|---|
528db7189a | |||
2bfba66dab | |||
43344566de | |||
bfb53747db | |||
d0321d17d0 | |||
f3ddbcf07c | |||
680044cba8 | |||
66555f19bf | |||
792779f60f | |||
44b68a8b71 | |||
205dd145b4 | |||
7346c37c86 | |||
72fbbe83f0 | |||
b4a930e774 | |||
8c4cf6261e | |||
8f5b257ed1 | |||
3bb00c4eb7 | |||
5d75aebf0d | |||
4d2a3771b8 | |||
ed6da528a2 | |||
aca687974a | |||
6e4b8faf40 | |||
552c4b419e | |||
6fb4a2406b | |||
65dbc6bdd4 | |||
dbab6f1762 | |||
d3ae25934c | |||
c6f68de516 | |||
2ccd7e65d3 | |||
bf3325a41e | |||
e4b3479417 | |||
0561b6a9be | |||
166f4d25ae | |||
a5f0d991fa | |||
60a0d7f293 | |||
d8c40e0724 | |||
f4ad230d58 | |||
b0b21ad8a0 | |||
08ee9cd776 | |||
cfd50ca359 | |||
fd94ba27ac | |||
bb5cd0f0d3 | |||
72b7281587 | |||
cbb4027099 | |||
5e1520aa3f | |||
a8f3730b7c | |||
c1432bd29e | |||
eb77290a93 | |||
5dcba8e2ad | |||
69ba400b69 | |||
2a7c893d94 | |||
2135ab1a5b | |||
6f5cefa367 | |||
c6f8591600 | |||
cd1fb697f7 | |||
42774674e5 |
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -2459,7 +2459,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "peach-config"
|
name = "peach-config"
|
||||||
version = "0.1.15"
|
version = "0.1.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger 0.6.2",
|
"env_logger 0.6.2",
|
||||||
@ -2492,8 +2492,8 @@ dependencies = [
|
|||||||
"jsonrpc-http-server 18.0.0",
|
"jsonrpc-http-server 18.0.0",
|
||||||
"jsonrpc-test 18.0.0",
|
"jsonrpc-test 18.0.0",
|
||||||
"log 0.4.14",
|
"log 0.4.14",
|
||||||
"miniserde",
|
|
||||||
"peach-stats",
|
"peach-stats",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2545,7 +2545,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "peach-network"
|
name = "peach-network"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"get_if_addrs",
|
"get_if_addrs",
|
||||||
"miniserde",
|
"miniserde",
|
||||||
@ -2585,7 +2585,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "peach-web"
|
name = "peach-web"
|
||||||
version = "0.4.17"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger 0.8.4",
|
"env_logger 0.8.4",
|
||||||
"log 0.4.14",
|
"log 0.4.14",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "peach-config"
|
name = "peach-config"
|
||||||
version = "0.1.15"
|
version = "0.1.17"
|
||||||
authors = ["Andrew Reid <gnomad@cryptolab.net>", "Max Fowler <max@mfowler.info>"]
|
authors = ["Andrew Reid <gnomad@cryptolab.net>", "Max Fowler <max@mfowler.info>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Command line tool for installing, updating and configuring PeachCloud"
|
description = "Command line tool for installing, updating and configuring PeachCloud"
|
||||||
|
@ -3,15 +3,12 @@
|
|||||||
pub const CONF: &str = "/var/lib/peachcloud/conf";
|
pub const CONF: &str = "/var/lib/peachcloud/conf";
|
||||||
|
|
||||||
// List of package names which are installed via apt-get
|
// List of package names which are installed via apt-get
|
||||||
pub const SERVICES: [&str; 11] = [
|
pub const SERVICES: [&str; 8] = [
|
||||||
"peach-oled",
|
|
||||||
"peach-network",
|
|
||||||
"peach-stats",
|
|
||||||
"peach-web",
|
"peach-web",
|
||||||
|
"peach-probe",
|
||||||
"peach-menu",
|
"peach-menu",
|
||||||
"peach-buttons",
|
"peach-buttons",
|
||||||
"peach-monitor",
|
"peach-oled",
|
||||||
"peach-probe",
|
|
||||||
"peach-dyndns-updater",
|
"peach-dyndns-updater",
|
||||||
"peach-go-sbot",
|
"peach-go-sbot",
|
||||||
"peach-config",
|
"peach-config",
|
||||||
|
@ -18,8 +18,8 @@ env_logger = "0.9"
|
|||||||
jsonrpc-core = "18"
|
jsonrpc-core = "18"
|
||||||
jsonrpc-http-server = "18"
|
jsonrpc-http-server = "18"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miniserde = "0.1.15"
|
peach-stats = { path = "../peach-stats", features = ["serde_support"] }
|
||||||
peach-stats = { path = "../peach-stats", features = ["miniserde_support"] }
|
serde_json = "1.0.74"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
jsonrpc-test = "18"
|
jsonrpc-test = "18"
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
|
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
|
||||||
|
use serde_json::error::Error as SerdeJsonError;
|
||||||
|
|
||||||
use peach_stats::StatsError;
|
use peach_stats::StatsError;
|
||||||
|
|
||||||
/// Custom error type encapsulating all possible errors for a JSON-RPC server
|
/// Custom error type encapsulating all possible errors for a JSON-RPC server
|
||||||
/// and associated methods.
|
/// and associated methods.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum JsonRpcServerError {
|
pub enum JsonRpcServerError {
|
||||||
|
/// Failed to serialize a string from a data structure.
|
||||||
|
Serde(SerdeJsonError),
|
||||||
/// An error returned from the `peach-stats` library.
|
/// An error returned from the `peach-stats` library.
|
||||||
Stats(StatsError),
|
Stats(StatsError),
|
||||||
/// An expected JSON-RPC method parameter was not provided.
|
/// An expected JSON-RPC method parameter was not provided.
|
||||||
@ -24,6 +28,9 @@ impl fmt::Display for JsonRpcServerError {
|
|||||||
JsonRpcServerError::MissingParameter(ref source) => {
|
JsonRpcServerError::MissingParameter(ref source) => {
|
||||||
write!(f, "Missing expected parameter: {}", source)
|
write!(f, "Missing expected parameter: {}", source)
|
||||||
}
|
}
|
||||||
|
JsonRpcServerError::Serde(ref source) => {
|
||||||
|
write!(f, "{}", source)
|
||||||
|
}
|
||||||
JsonRpcServerError::Stats(ref source) => {
|
JsonRpcServerError::Stats(ref source) => {
|
||||||
write!(f, "{}", source)
|
write!(f, "{}", source)
|
||||||
}
|
}
|
||||||
@ -34,6 +41,11 @@ impl fmt::Display for JsonRpcServerError {
|
|||||||
impl From<JsonRpcServerError> for JsonRpcError {
|
impl From<JsonRpcServerError> for JsonRpcError {
|
||||||
fn from(err: JsonRpcServerError) -> Self {
|
fn from(err: JsonRpcServerError) -> Self {
|
||||||
match &err {
|
match &err {
|
||||||
|
JsonRpcServerError::Serde(source) => JsonRpcError {
|
||||||
|
code: ErrorCode::ServerError(-32002),
|
||||||
|
message: format!("{}", source),
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
JsonRpcServerError::Stats(source) => JsonRpcError {
|
JsonRpcServerError::Stats(source) => JsonRpcError {
|
||||||
code: ErrorCode::ServerError(-32001),
|
code: ErrorCode::ServerError(-32001),
|
||||||
message: format!("{}", source),
|
message: format!("{}", source),
|
||||||
|
@ -8,7 +8,6 @@ use std::result::Result;
|
|||||||
use jsonrpc_core::{IoHandler, Value};
|
use jsonrpc_core::{IoHandler, Value};
|
||||||
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
|
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
|
||||||
use log::info;
|
use log::info;
|
||||||
use miniserde::json;
|
|
||||||
use peach_stats::stats;
|
use peach_stats::stats;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
@ -30,7 +29,7 @@ pub fn run() -> Result<(), JsonRpcServerError> {
|
|||||||
io.add_sync_method("cpu_stats", move |_| {
|
io.add_sync_method("cpu_stats", move |_| {
|
||||||
info!("Fetching CPU statistics.");
|
info!("Fetching CPU statistics.");
|
||||||
let cpu = stats::cpu_stats().map_err(JsonRpcServerError::Stats)?;
|
let cpu = stats::cpu_stats().map_err(JsonRpcServerError::Stats)?;
|
||||||
let json_cpu = json::to_string(&cpu);
|
let json_cpu = serde_json::to_string(&cpu).map_err(JsonRpcServerError::Serde)?;
|
||||||
|
|
||||||
Ok(Value::String(json_cpu))
|
Ok(Value::String(json_cpu))
|
||||||
});
|
});
|
||||||
@ -38,7 +37,7 @@ pub fn run() -> Result<(), JsonRpcServerError> {
|
|||||||
io.add_sync_method("cpu_stats_percent", move |_| {
|
io.add_sync_method("cpu_stats_percent", move |_| {
|
||||||
info!("Fetching CPU statistics as percentages.");
|
info!("Fetching CPU statistics as percentages.");
|
||||||
let cpu = stats::cpu_stats_percent().map_err(JsonRpcServerError::Stats)?;
|
let cpu = stats::cpu_stats_percent().map_err(JsonRpcServerError::Stats)?;
|
||||||
let json_cpu = json::to_string(&cpu);
|
let json_cpu = serde_json::to_string(&cpu).map_err(JsonRpcServerError::Serde)?;
|
||||||
|
|
||||||
Ok(Value::String(json_cpu))
|
Ok(Value::String(json_cpu))
|
||||||
});
|
});
|
||||||
@ -46,7 +45,7 @@ pub fn run() -> Result<(), JsonRpcServerError> {
|
|||||||
io.add_sync_method("disk_usage", move |_| {
|
io.add_sync_method("disk_usage", move |_| {
|
||||||
info!("Fetching disk usage statistics.");
|
info!("Fetching disk usage statistics.");
|
||||||
let disks = stats::disk_usage().map_err(JsonRpcServerError::Stats)?;
|
let disks = stats::disk_usage().map_err(JsonRpcServerError::Stats)?;
|
||||||
let json_disks = json::to_string(&disks);
|
let json_disks = serde_json::to_string(&disks).map_err(JsonRpcServerError::Serde)?;
|
||||||
|
|
||||||
Ok(Value::String(json_disks))
|
Ok(Value::String(json_disks))
|
||||||
});
|
});
|
||||||
@ -54,7 +53,7 @@ pub fn run() -> Result<(), JsonRpcServerError> {
|
|||||||
io.add_sync_method("load_average", move |_| {
|
io.add_sync_method("load_average", move |_| {
|
||||||
info!("Fetching system load average statistics.");
|
info!("Fetching system load average statistics.");
|
||||||
let avg = stats::load_average().map_err(JsonRpcServerError::Stats)?;
|
let avg = stats::load_average().map_err(JsonRpcServerError::Stats)?;
|
||||||
let json_avg = json::to_string(&avg);
|
let json_avg = serde_json::to_string(&avg).map_err(JsonRpcServerError::Serde)?;
|
||||||
|
|
||||||
Ok(Value::String(json_avg))
|
Ok(Value::String(json_avg))
|
||||||
});
|
});
|
||||||
@ -62,7 +61,7 @@ pub fn run() -> Result<(), JsonRpcServerError> {
|
|||||||
io.add_sync_method("mem_stats", move |_| {
|
io.add_sync_method("mem_stats", move |_| {
|
||||||
info!("Fetching current memory statistics.");
|
info!("Fetching current memory statistics.");
|
||||||
let mem = stats::mem_stats().map_err(JsonRpcServerError::Stats)?;
|
let mem = stats::mem_stats().map_err(JsonRpcServerError::Stats)?;
|
||||||
let json_mem = json::to_string(&mem);
|
let json_mem = serde_json::to_string(&mem).map_err(JsonRpcServerError::Serde)?;
|
||||||
|
|
||||||
Ok(Value::String(json_mem))
|
Ok(Value::String(json_mem))
|
||||||
});
|
});
|
||||||
@ -70,7 +69,7 @@ pub fn run() -> Result<(), JsonRpcServerError> {
|
|||||||
io.add_sync_method("uptime", move |_| {
|
io.add_sync_method("uptime", move |_| {
|
||||||
info!("Fetching system uptime.");
|
info!("Fetching system uptime.");
|
||||||
let uptime = stats::uptime().map_err(JsonRpcServerError::Stats)?;
|
let uptime = stats::uptime().map_err(JsonRpcServerError::Stats)?;
|
||||||
let json_uptime = json::to_string(&uptime);
|
let json_uptime = serde_json::to_string(&uptime).map_err(JsonRpcServerError::Serde)?;
|
||||||
|
|
||||||
Ok(Value::String(json_uptime))
|
Ok(Value::String(json_uptime))
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@ use crate::{config_manager, error::PeachError, sbot_client};
|
|||||||
/// and returns Err if the supplied password is incorrect.
|
/// and returns Err if the supplied password is incorrect.
|
||||||
pub fn verify_password(password: &str) -> Result<(), PeachError> {
|
pub fn verify_password(password: &str) -> Result<(), PeachError> {
|
||||||
let real_admin_password_hash = config_manager::get_admin_password_hash()?;
|
let real_admin_password_hash = config_manager::get_admin_password_hash()?;
|
||||||
let password_hash = hash_password(&password.to_string());
|
let password_hash = hash_password(password);
|
||||||
if real_admin_password_hash == password_hash {
|
if real_admin_password_hash == password_hash {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -29,7 +29,7 @@ pub fn validate_new_passwords(new_password1: &str, new_password2: &str) -> Resul
|
|||||||
|
|
||||||
/// Sets a new password for the admin user
|
/// Sets a new password for the admin user
|
||||||
pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
|
pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
|
||||||
let new_password_hash = hash_password(&new_password.to_string());
|
let new_password_hash = hash_password(new_password);
|
||||||
config_manager::set_admin_password_hash(&new_password_hash)?;
|
config_manager::set_admin_password_hash(&new_password_hash)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -49,7 +49,7 @@ pub fn hash_password(password: &str) -> String {
|
|||||||
/// Sets a new temporary password for the admin user
|
/// Sets a new temporary password for the admin user
|
||||||
/// which can be used to reset the permanent password
|
/// which can be used to reset the permanent password
|
||||||
pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> {
|
pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> {
|
||||||
let new_password_hash = hash_password(&new_password.to_string());
|
let new_password_hash = hash_password(new_password);
|
||||||
config_manager::set_temporary_password_hash(&new_password_hash)?;
|
config_manager::set_temporary_password_hash(&new_password_hash)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -59,7 +59,7 @@ pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError>
|
|||||||
/// and returns Err if the supplied temp_password is incorrect
|
/// and returns Err if the supplied temp_password is incorrect
|
||||||
pub fn verify_temporary_password(password: &str) -> Result<(), PeachError> {
|
pub fn verify_temporary_password(password: &str) -> Result<(), PeachError> {
|
||||||
let temporary_admin_password_hash = config_manager::get_temporary_password_hash()?;
|
let temporary_admin_password_hash = config_manager::get_temporary_password_hash()?;
|
||||||
let password_hash = hash_password(&password.to_string());
|
let password_hash = hash_password(password);
|
||||||
if temporary_admin_password_hash == password_hash {
|
if temporary_admin_password_hash == password_hash {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
@ -67,7 +67,7 @@ pub fn create_invite(uses: i32) -> Result<String, PeachError> {
|
|||||||
.arg(uses.to_string())
|
.arg(uses.to_string())
|
||||||
.output()?;
|
.output()?;
|
||||||
let text_output = std::str::from_utf8(&output.stdout)?;
|
let text_output = std::str::from_utf8(&output.stdout)?;
|
||||||
let output = text_output.replace("\n", "");
|
let output = text_output.replace('\n', "");
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "peach-network"
|
name = "peach-network"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Query and configure network interfaces."
|
description = "Query and configure network interfaces."
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# peach-network
|
# peach-network
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Network interface state query and modification library.
|
Network interface state query and modification library.
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ pub enum NetworkError {
|
|||||||
/// Failed to retrieve connection state of wlan0 interface.
|
/// Failed to retrieve connection state of wlan0 interface.
|
||||||
WlanOperstate(IoError),
|
WlanOperstate(IoError),
|
||||||
/// Failed to save wpa_supplicant configuration changes to file.
|
/// Failed to save wpa_supplicant configuration changes to file.
|
||||||
Save,
|
Save(IoError),
|
||||||
/// Failed to connect to network.
|
/// Failed to connect to network.
|
||||||
Connect {
|
Connect {
|
||||||
/// ID.
|
/// ID.
|
||||||
@ -197,7 +197,7 @@ impl std::error::Error for NetworkError {
|
|||||||
NetworkError::Delete { .. } => None,
|
NetworkError::Delete { .. } => None,
|
||||||
NetworkError::WlanState(ref source) => Some(source),
|
NetworkError::WlanState(ref source) => Some(source),
|
||||||
NetworkError::WlanOperstate(ref source) => Some(source),
|
NetworkError::WlanOperstate(ref source) => Some(source),
|
||||||
NetworkError::Save => None,
|
NetworkError::Save(ref source) => Some(source),
|
||||||
NetworkError::Connect { .. } => None,
|
NetworkError::Connect { .. } => None,
|
||||||
NetworkError::StartInterface { ref source, .. } => Some(source),
|
NetworkError::StartInterface { ref source, .. } => Some(source),
|
||||||
NetworkError::WpaCtrl(ref source) => Some(source),
|
NetworkError::WpaCtrl(ref source) => Some(source),
|
||||||
@ -326,7 +326,11 @@ impl std::fmt::Display for NetworkError {
|
|||||||
NetworkError::WlanOperstate(_) => {
|
NetworkError::WlanOperstate(_) => {
|
||||||
write!(f, "Failed to retrieve connection state of wlan0 interface")
|
write!(f, "Failed to retrieve connection state of wlan0 interface")
|
||||||
}
|
}
|
||||||
NetworkError::Save => write!(f, "Failed to save configuration changes to file"),
|
NetworkError::Save(ref source) => write!(
|
||||||
|
f,
|
||||||
|
"Failed to save configuration changes to file: {}",
|
||||||
|
source
|
||||||
|
),
|
||||||
NetworkError::Connect { ref id, ref iface } => {
|
NetworkError::Connect { ref id, ref iface } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
@ -138,7 +138,7 @@ pub fn available_networks(iface: &str) -> Result<Option<Vec<Scan>>, NetworkError
|
|||||||
// we only want to return the auth / crypto flags
|
// we only want to return the auth / crypto flags
|
||||||
if flags_vec[0] != "[ESS]" {
|
if flags_vec[0] != "[ESS]" {
|
||||||
// parse auth / crypto flag and assign it to protocol
|
// parse auth / crypto flag and assign it to protocol
|
||||||
protocol.push_str(flags_vec[0].replace("[", "").replace("]", "").as_str());
|
protocol.push_str(flags_vec[0].replace('[', "").replace(']', "").as_str());
|
||||||
}
|
}
|
||||||
let ssid = v[4].to_string();
|
let ssid = v[4].to_string();
|
||||||
let response = Scan {
|
let response = Scan {
|
||||||
@ -513,16 +513,14 @@ pub fn add(wlan_iface: &str, ssid: &str, pass: &str) -> Result<(), NetworkError>
|
|||||||
// append wpa_passphrase output to wpa_supplicant-<wlan_iface>.conf if successful
|
// append wpa_passphrase output to wpa_supplicant-<wlan_iface>.conf if successful
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
// open file in append mode
|
// open file in append mode
|
||||||
let file = OpenOptions::new().append(true).open(wlan_config);
|
let mut file = OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.open(wlan_config)
|
||||||
|
// TODO: create the file if it doesn't exist
|
||||||
|
.map_err(NetworkError::Save)?;
|
||||||
|
|
||||||
|
file.write(&wpa_details).map_err(NetworkError::Save)?;
|
||||||
|
|
||||||
let _file = match file {
|
|
||||||
// if file exists & open succeeds, write wifi configuration
|
|
||||||
Ok(mut f) => f.write(&wpa_details),
|
|
||||||
// TODO: handle this better: create file if not found
|
|
||||||
// & seed with 'ctrl_interace' & 'update_config' settings
|
|
||||||
// config file could also be copied from peach/config fs location
|
|
||||||
Err(e) => panic!("Failed to write to file: {}", e),
|
|
||||||
};
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let err_msg = String::from_utf8_lossy(&output.stdout);
|
let err_msg = String::from_utf8_lossy(&output.stdout);
|
||||||
@ -642,6 +640,38 @@ pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Forget credentials for the given network SSID and interface.
|
||||||
|
/// Look up the network identified for the given SSID, delete the credentials
|
||||||
|
/// and then save.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `iface` - A string slice holding the name of a wireless network interface
|
||||||
|
/// * `ssid` - A string slice holding the SSID for a wireless access point
|
||||||
|
///
|
||||||
|
/// If the credentials are successfully deleted and saved, an `Ok` `Result`
|
||||||
|
/// type is returned. In the event of an error, a `NetworkError` is returned
|
||||||
|
/// in the `Result`.
|
||||||
|
pub fn forget(iface: &str, ssid: &str) -> Result<(), NetworkError> {
|
||||||
|
// get the id of the network
|
||||||
|
let id_opt = id(iface, ssid)?;
|
||||||
|
let id = id_opt.ok_or(NetworkError::Id {
|
||||||
|
ssid: ssid.to_string(),
|
||||||
|
iface: iface.to_string(),
|
||||||
|
})?;
|
||||||
|
// delete the old credentials
|
||||||
|
// TODO: i've switched these back to the "correct" order
|
||||||
|
// WEIRD BUG: the parameters below are technically in the wrong order:
|
||||||
|
// it should be id first and then iface, but somehow they get twisted.
|
||||||
|
// i don't understand computers.
|
||||||
|
//delete(&iface, &id)?;
|
||||||
|
delete(&id, iface)?;
|
||||||
|
// save the updates to wpa_supplicant.conf
|
||||||
|
save()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Modify password for a given network identifier and interface.
|
/// Modify password for a given network identifier and interface.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -708,7 +738,7 @@ pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
|
|||||||
|
|
||||||
/// Save configuration updates to the `wpa_supplicant` configuration file.
|
/// Save configuration updates to the `wpa_supplicant` configuration file.
|
||||||
///
|
///
|
||||||
/// If wireless network configuration updates are successfully save to the
|
/// If wireless network configuration updates are successfully saved to the
|
||||||
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
|
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
|
||||||
/// event of an error, a `NetworkError` is returned in the `Result`.
|
/// event of an error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn save() -> Result<(), NetworkError> {
|
pub fn save() -> Result<(), NetworkError> {
|
||||||
@ -716,3 +746,18 @@ pub fn save() -> Result<(), NetworkError> {
|
|||||||
wpa.request("SAVE_CONFIG")?;
|
wpa.request("SAVE_CONFIG")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update password for an access point and save configuration updates to the
|
||||||
|
/// `wpa_supplicant` configuration file.
|
||||||
|
///
|
||||||
|
/// If wireless network configuration updates are successfully saved to the
|
||||||
|
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
|
||||||
|
/// event of an error, a `NetworkError` is returned in the `Result`.
|
||||||
|
pub fn update(iface: &str, ssid: &str, pass: &str) -> Result<(), NetworkError> {
|
||||||
|
// delete the old credentials and save the changes
|
||||||
|
forget(iface, ssid)?;
|
||||||
|
// add the new credentials
|
||||||
|
add(iface, ssid, pass)?;
|
||||||
|
reconfigure()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "peach-web"
|
name = "peach-web"
|
||||||
version = "0.4.17"
|
version = "0.5.0"
|
||||||
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins."
|
description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins."
|
||||||
@ -27,7 +27,6 @@ assets = [
|
|||||||
["static/css/*", "/usr/share/peach-web/static/css/", "644"],
|
["static/css/*", "/usr/share/peach-web/static/css/", "644"],
|
||||||
["static/icons/*", "/usr/share/peach-web/static/icons/", "644"],
|
["static/icons/*", "/usr/share/peach-web/static/icons/", "644"],
|
||||||
["static/images/*", "/usr/share/peach-web/static/images/", "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"],
|
["README.md", "/usr/share/doc/peach-web/README", "644"],
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -39,16 +38,12 @@ maintenance = { status = "actively-developed" }
|
|||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
nest = "1.0.0"
|
nest = "1.0.0"
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
|
||||||
peach-lib = { path = "../peach-lib" }
|
peach-lib = { path = "../peach-lib" }
|
||||||
peach-network = { path = "../peach-network", features = ["serde_support"] }
|
peach-network = { path = "../peach-network", features = ["serde_support"] }
|
||||||
peach-stats = { path = "../peach-stats", 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"] }
|
rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
snafu = "0.6"
|
|
||||||
tera = { version = "1.12.1", features = ["builtins"] }
|
tera = { version = "1.12.1", features = ["builtins"] }
|
||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
# peach-web
|
# peach-web
|
||||||
|
|
||||||
[](https://travis-ci.com/peachcloud/peach-web) 
|
[](https://travis-ci.com/peachcloud/peach-web) 
|
||||||
|
|
||||||
## Web Interface for PeachCloud
|
## Web Interface for PeachCloud
|
||||||
|
|
||||||
**peach-web** provides a web interface for the PeachCloud device. It serves static assets and exposes a JSON API for programmatic interactions.
|
**peach-web** provides a web interface for the PeachCloud device.
|
||||||
|
|
||||||
Initial development is focused on administration of the device itself, beginning with networking functionality, with SSB-related administration to be integrated at a later stage.
|
Initial development is focused on administration of the device itself, beginning with networking functionality, with SSB-related administration to be integrated at a later stage.
|
||||||
|
|
||||||
The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web framework), [Tera](http://tera.netlify.com/) (Rust template engine), HTML, CSS and JavaScript.
|
The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web framework), [Tera](http://tera.netlify.com/) (Rust template engine), HTML and CSS.
|
||||||
|
|
||||||
_Note: This is a work-in-progress._
|
_Note: This is a work-in-progress._
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ Move into the repo and compile:
|
|||||||
|
|
||||||
Run the tests:
|
Run the tests:
|
||||||
|
|
||||||
`cargo test`
|
`ROCKET_DISABLE_AUTH=true ROCKET_STANDALONE_MODE=false cargo test`
|
||||||
|
|
||||||
Move back to the `peach-workspace` directory:
|
Move back to the `peach-workspace` directory:
|
||||||
|
|
||||||
@ -35,21 +35,25 @@ Run the binary:
|
|||||||
|
|
||||||
`./target/release/peach-web`
|
`./target/release/peach-web`
|
||||||
|
|
||||||
_Note: Networking functionality requires peach-network microservice to be running._
|
|
||||||
|
|
||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
**Deployment Mode**
|
**Deployment Profile**
|
||||||
|
|
||||||
The web application deployment mode is configured with the `ROCKET_ENV` environment variable:
|
The web application deployment profile can be configured with the `ROCKET_ENV` environment variable:
|
||||||
|
|
||||||
`export ROCKET_ENV=stage`
|
`export ROCKET_ENV=stage`
|
||||||
|
|
||||||
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.
|
Default configuration parameters are defined in `Rocket.toml`. This file defines a set of default parameters, some of which are overwritten when running in `debug` mode (ie. `cargo run` or `cargo build`) or `release` mode (ie. `cargo run --release` or `cargo build --release`).
|
||||||
|
|
||||||
|
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 enabled by default (as defined in `Rocket.toml`) but can be overwritten using the `ROCKET_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**
|
||||||
|
|
||||||
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`:
|
Authentication is disabled in `debug` mode and enabled by default when running the application in `release` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`:
|
||||||
|
|
||||||
`export ROCKET_DISABLE_AUTH=true`
|
`export ROCKET_DISABLE_AUTH=true`
|
||||||
|
|
||||||
@ -95,13 +99,13 @@ Remove configuration files (not removed with `apt-get remove`):
|
|||||||
|
|
||||||
### Design
|
### Design
|
||||||
|
|
||||||
`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call JSON-RPC microservices and serve HTML and assets. A JSON API is exposed for remote calls and dynamic client-side content updates (via plain JavaScript following unobstructive design principles). Each Tera template is passed a context object. In the case of Rust, this object is a `struct` and must implement `Serialize`. The fields of the context object are available in the context of the template to be rendered.
|
`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call `peach-` libraries and serve HTML and assets. Each Tera template is passed a context object. In the case of Rust, this object is a `struct` and must implement `Serialize`. The fields of the context object are available in the context of the template to be rendered.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Configuration variables are stored in /var/lib/peachcloud/config.yml.
|
Configuration variables are stored in /var/lib/peachcloud/config.yml.
|
||||||
Peach-web also updates this file when changes are made to configurations via
|
Peach-web also updates this file when changes are made to configurations via
|
||||||
the web interface. peach-web has no database, so all configurations are stored in this file.
|
the web interface. peach-web has no database, so all configurations are stored in this file.
|
||||||
|
|
||||||
#### Dynamic DNS Configuration
|
#### Dynamic DNS Configuration
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
[default]
|
[default]
|
||||||
secret_key = "VYVUDivXvu8g6llxeJd9F92pMfocml5xl/Jjv5Sk4yw="
|
secret_key = "VYVUDivXvu8g6llxeJd9F92pMfocml5xl/Jjv5Sk4yw="
|
||||||
|
disable_auth = false
|
||||||
|
standalone_mode = true
|
||||||
|
|
||||||
[development]
|
[debug]
|
||||||
template_dir = "templates/"
|
template_dir = "templates/"
|
||||||
disable_auth = true
|
disable_auth = true
|
||||||
|
|
||||||
[production]
|
[release]
|
||||||
template_dir = "templates/"
|
template_dir = "templates/"
|
||||||
disable_auth = false
|
|
||||||
|
36
peach-web/src/context/dns.rs
Normal file
36
peach-web/src/context/dns.rs
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
peach-web/src/context/mod.rs
Normal file
2
peach-web/src/context/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod dns;
|
||||||
|
pub mod network;
|
398
peach-web/src/context/network.rs
Normal file
398
peach-web/src/context/network.rs
Normal file
@ -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::error::PeachError;
|
||||||
use peach_lib::{serde_json, serde_yaml};
|
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 {
|
pub enum PeachWebError {
|
||||||
#[snafu(display("Error loading serde json"))]
|
Json(JsonError),
|
||||||
Serde { source: serde_json::error::Error },
|
Yaml(YamlError),
|
||||||
#[snafu(display("Error loading peach-config yaml"))]
|
FailedToRegisterDynDomain(String),
|
||||||
YamlError { source: serde_yaml::Error },
|
PeachLib { source: PeachError, msg: String },
|
||||||
#[snafu(display("{}", msg))]
|
|
||||||
FailedToRegisterDynDomain { msg: String },
|
|
||||||
#[snafu(display("{}: {}", source, msg))]
|
|
||||||
PeachLibError { source: PeachError, msg: String },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serde_json::error::Error> for PeachWebError {
|
impl std::error::Error for PeachWebError {
|
||||||
fn from(err: serde_json::error::Error) -> PeachWebError {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
PeachWebError::Serde { source: err }
|
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 {
|
impl std::fmt::Display for PeachWebError {
|
||||||
fn from(err: serde_yaml::Error) -> PeachWebError {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
PeachWebError::YamlError { source: err }
|
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 {
|
impl From<PeachError> for PeachWebError {
|
||||||
fn from(err: PeachError) -> PeachWebError {
|
fn from(err: PeachError) -> PeachWebError {
|
||||||
PeachWebError::PeachLibError {
|
PeachWebError::PeachLib {
|
||||||
source: err,
|
source: err,
|
||||||
msg: "".to_string(),
|
msg: "".to_string(),
|
||||||
}
|
}
|
||||||
|
@ -24,145 +24,59 @@
|
|||||||
|
|
||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
|
|
||||||
|
mod context;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
mod router;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use log::{error, info};
|
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use rocket::{catchers, fs::FileServer, routes, Build, Rocket};
|
use log::{debug, error, info};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket};
|
||||||
|
|
||||||
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::status::ping::*;
|
|
||||||
|
|
||||||
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::*;
|
|
||||||
|
|
||||||
pub type BoxError = Box<dyn std::error::Error>;
|
pub type BoxError = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
/// Create rocket instance & mount all routes.
|
/// Application configuration parameters.
|
||||||
fn init_rocket() -> Rocket<Build> {
|
/// These values are extracted from Rocket's default configuration provider:
|
||||||
rocket::build()
|
/// `Config::figment()`. As such, the values are drawn from `Rocket.toml` or
|
||||||
// GENERAL HTML ROUTES
|
/// the TOML file path in the `ROCKET_CONFIG` environment variable. The TOML
|
||||||
.mount(
|
/// file parameters are automatically overruled by any `ROCKET_` variables
|
||||||
"/",
|
/// which might be set.
|
||||||
routes![
|
#[derive(Debug, Deserialize)]
|
||||||
help,
|
pub struct RocketConfig {
|
||||||
home,
|
disable_auth: bool,
|
||||||
login,
|
standalone_mode: bool,
|
||||||
login_post,
|
}
|
||||||
logout,
|
|
||||||
reboot_cmd,
|
static WLAN_IFACE: &str = "wlan0";
|
||||||
shutdown_cmd,
|
static AP_IFACE: &str = "ap0";
|
||||||
power_menu,
|
|
||||||
settings_menu,
|
pub fn init_rocket() -> Rocket<Build> {
|
||||||
],
|
info!("Initializing Rocket");
|
||||||
)
|
// build a basic rocket instance
|
||||||
// STATUS HTML ROUTES
|
let rocket = rocket::build();
|
||||||
.mount("/status", routes![device_status, network_status])
|
|
||||||
// ADMIN SETTINGS HTML ROUTES
|
// return the default provider figment used by `rocket::build()`
|
||||||
.mount(
|
let figment = rocket.figment();
|
||||||
"/settings/admin",
|
|
||||||
routes![
|
// deserialize configuration parameters into our `RocketConfig` struct (defined above)
|
||||||
admin_menu,
|
// since we're in the intialisation phase, panic if the extraction fails
|
||||||
configure_admin,
|
let config: RocketConfig = figment.extract().expect("configuration extraction failed");
|
||||||
add_admin,
|
|
||||||
add_admin_post,
|
debug!("{:?}", config);
|
||||||
delete_admin_post,
|
|
||||||
change_password,
|
info!("Mounting Rocket routes");
|
||||||
change_password_post,
|
let mounted_rocket = if config.standalone_mode {
|
||||||
reset_password,
|
router::mount_peachpub_routes(rocket)
|
||||||
reset_password_post,
|
} else {
|
||||||
forgot_password_page,
|
router::mount_peachcloud_routes(rocket)
|
||||||
send_password_reset_post,
|
};
|
||||||
],
|
|
||||||
)
|
info!("Attaching application configuration to managed state");
|
||||||
// NETWORK SETTINGS HTML ROUTES
|
mounted_rocket.attach(AdHoc::config::<RocketConfig>())
|
||||||
.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,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
// GENERAL JSON API ROUTES
|
|
||||||
.mount(
|
|
||||||
"/api/v1",
|
|
||||||
routes![ping_pong, ping_network, ping_oled, ping_stats,],
|
|
||||||
)
|
|
||||||
// ADMIN JSON API ROUTES
|
|
||||||
.mount(
|
|
||||||
"/api/v1/admin",
|
|
||||||
routes![
|
|
||||||
save_password_form_endpoint,
|
|
||||||
reset_password_form_endpoint,
|
|
||||||
reboot_device,
|
|
||||||
shutdown_device,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
// NETWORK JSON API ROUTES
|
|
||||||
.mount(
|
|
||||||
"/api/v1/network",
|
|
||||||
routes![
|
|
||||||
activate_ap,
|
|
||||||
activate_client,
|
|
||||||
add_wifi_credentials,
|
|
||||||
connect_ap,
|
|
||||||
disconnect_ap,
|
|
||||||
forget_ap,
|
|
||||||
modify_password,
|
|
||||||
reset_data_total,
|
|
||||||
return_ip,
|
|
||||||
return_rssi,
|
|
||||||
return_ssid,
|
|
||||||
return_state,
|
|
||||||
return_status,
|
|
||||||
scan_networks,
|
|
||||||
update_wifi_alerts,
|
|
||||||
save_dns_configuration_endpoint,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.mount("/", FileServer::from("static"))
|
|
||||||
.register("/", catchers![not_found, internal_error, forbidden])
|
|
||||||
.attach(Template::fairing())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Launch the peach-web rocket server.
|
/// Launch the peach-web rocket server.
|
||||||
@ -172,7 +86,6 @@ async fn main() {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
// initialize rocket
|
// initialize rocket
|
||||||
info!("Initializing Rocket");
|
|
||||||
let rocket = init_rocket();
|
let rocket = init_rocket();
|
||||||
|
|
||||||
// launch rocket
|
// launch rocket
|
||||||
|
95
peach-web/src/router.rs
Normal file
95
peach-web/src/router.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
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::*, scuttlebutt::*},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create a Rocket instance and mount PeachPub routes, fileserver and
|
||||||
|
/// catchers. This gives us everything we need to run PeachPub and excludes
|
||||||
|
/// settings and status routes related to networking and the device (memory,
|
||||||
|
/// hard disk, CPU etc.).
|
||||||
|
pub fn mount_peachpub_routes(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
|
rocket
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
routes![
|
||||||
|
help,
|
||||||
|
home,
|
||||||
|
login,
|
||||||
|
login_post,
|
||||||
|
logout,
|
||||||
|
reboot_cmd,
|
||||||
|
shutdown_cmd,
|
||||||
|
power_menu,
|
||||||
|
settings_menu,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.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,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.mount(
|
||||||
|
"/settings/scuttlebutt",
|
||||||
|
routes![ssb_settings_menu, configure_sbot],
|
||||||
|
)
|
||||||
|
.mount(
|
||||||
|
"/scuttlebutt",
|
||||||
|
routes![
|
||||||
|
peers, friends, follows, followers, blocks, profile, private, follow, unfollow,
|
||||||
|
block, publish,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.mount("/status", routes![scuttlebutt_status])
|
||||||
|
.mount("/", FileServer::from("static"))
|
||||||
|
.register("/", catchers![not_found, internal_error, forbidden])
|
||||||
|
.attach(Template::fairing())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a Rocket instance with PeachPub routes, fileserver and catchers by
|
||||||
|
/// calling `mount_peachpub_routes()` and then mount all additional routes
|
||||||
|
/// required to run a complete PeachCloud build.
|
||||||
|
pub fn mount_peachcloud_routes(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
|
mount_peachpub_routes(rocket)
|
||||||
|
.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,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.mount("/status", routes![device_status, network_status])
|
||||||
|
}
|
@ -1,20 +1,21 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
use rocket::form::{Form, FromForm};
|
use rocket::{
|
||||||
use rocket::http::{Cookie, CookieJar, Status};
|
form::{Form, FromForm},
|
||||||
use rocket::request::{self, FlashMessage, FromRequest, Request};
|
get,
|
||||||
use rocket::response::{Flash, Redirect};
|
http::{Cookie, CookieJar, Status},
|
||||||
use rocket::serde::{
|
post,
|
||||||
json::{Json, Value},
|
request::{self, FlashMessage, FromRequest, Request},
|
||||||
Deserialize, Serialize,
|
response::{Flash, Redirect},
|
||||||
|
serde::Deserialize,
|
||||||
};
|
};
|
||||||
use rocket::{get, post, Config};
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
use rocket_dyn_templates::Template;
|
|
||||||
|
|
||||||
use peach_lib::error::PeachError;
|
use peach_lib::{error::PeachError, password_utils};
|
||||||
use peach_lib::password_utils;
|
|
||||||
|
|
||||||
use crate::error::PeachWebError;
|
use crate::error::PeachWebError;
|
||||||
use crate::utils::{build_json_response, TemplateOrRedirect};
|
use crate::utils::TemplateOrRedirect;
|
||||||
|
//use crate::DisableAuth;
|
||||||
|
use crate::RocketConfig;
|
||||||
|
|
||||||
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
|
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
|
||||||
|
|
||||||
@ -42,14 +43,14 @@ impl<'r> FromRequest<'r> for Authenticated {
|
|||||||
type Error = LoginError;
|
type Error = LoginError;
|
||||||
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
// check for `disable_auth` config value; set to `false` if unset
|
// retrieve auth state from managed state (returns `Option<bool>`).
|
||||||
// can be set via the `ROCKET_DISABLE_AUTH` environment variable
|
// this value is read from the Rocket.toml config file on start-up
|
||||||
// - env var, if set, takes precedence over value defined in `Rocket.toml`
|
let authentication_is_disabled: bool = *req
|
||||||
let authentication_is_disabled: bool = match Config::figment().find_value("disable_auth") {
|
.rocket()
|
||||||
// deserialize the boolean value; set to `false` if an error is encountered
|
.state::<RocketConfig>()
|
||||||
Ok(value) => value.deserialize().unwrap_or(false),
|
.map(|config| (&config.disable_auth))
|
||||||
Err(_) => false,
|
.unwrap_or(&false);
|
||||||
};
|
|
||||||
if authentication_is_disabled {
|
if authentication_is_disabled {
|
||||||
let auth = Authenticated {};
|
let auth = Authenticated {};
|
||||||
request::Outcome::Success(auth)
|
request::Outcome::Success(auth)
|
||||||
@ -69,37 +70,19 @@ impl<'r> FromRequest<'r> for Authenticated {
|
|||||||
|
|
||||||
// HELPERS AND ROUTES FOR /login
|
// 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")]
|
#[get("/login")]
|
||||||
pub fn login(flash: Option<FlashMessage>) -> Template {
|
pub fn login(flash: Option<FlashMessage>) -> Template {
|
||||||
let mut context = LoginContext::build();
|
let mut context = Context::new();
|
||||||
context.back = Some("/".to_string());
|
context.insert("back", &Some("/".to_string()));
|
||||||
context.title = Some("Login".to_string());
|
context.insert("title", &Some("Login".to_string()));
|
||||||
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.flash_msg = Some(flash.message().to_string());
|
|
||||||
};
|
};
|
||||||
Template::render("login", &context)
|
|
||||||
|
Template::render("login", &context.into_json())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, FromForm)]
|
#[derive(Debug, Deserialize, FromForm)]
|
||||||
@ -119,8 +102,7 @@ pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> {
|
|||||||
|
|
||||||
#[post("/login", data = "<login_form>")]
|
#[post("/login", data = "<login_form>")]
|
||||||
pub fn login_post(login_form: Form<LoginForm>, cookies: &CookieJar<'_>) -> TemplateOrRedirect {
|
pub fn login_post(login_form: Form<LoginForm>, cookies: &CookieJar<'_>) -> TemplateOrRedirect {
|
||||||
let result = verify_login_form(login_form.into_inner());
|
match verify_login_form(login_form.into_inner()) {
|
||||||
match result {
|
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// if successful login, add a cookie indicating the user is authenticated
|
// if successful login, add a cookie indicating the user is authenticated
|
||||||
// and redirect to home page
|
// and redirect to home page
|
||||||
@ -128,17 +110,18 @@ pub fn login_post(login_form: Form<LoginForm>, cookies: &CookieJar<'_>) -> Templ
|
|||||||
// is just admin (this is arbitrary).
|
// is just admin (this is arbitrary).
|
||||||
// If we had multiple users, we could put the user_id here.
|
// If we had multiple users, we could put the user_id here.
|
||||||
cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME));
|
cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME));
|
||||||
|
|
||||||
TemplateOrRedirect::Redirect(Redirect::to("/"))
|
TemplateOrRedirect::Redirect(Redirect::to("/"))
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// if unsuccessful login, render /login page again
|
// if unsuccessful login, render /login page again
|
||||||
let mut context = LoginContext::build();
|
let mut context = Context::new();
|
||||||
context.back = Some("/".to_string());
|
context.insert("back", &Some("/".to_string()));
|
||||||
context.title = Some("Login".to_string());
|
context.insert("title", &Some("Login".to_string()));
|
||||||
context.flash_name = Some("error".to_string());
|
context.insert("flash_name", &("error".to_string()));
|
||||||
let flash_msg = "Invalid password".to_string();
|
context.insert("flash_msg", &("Invalid password".to_string()));
|
||||||
context.flash_msg = Some(flash_msg);
|
|
||||||
TemplateOrRedirect::Template(Template::render("login", &context))
|
TemplateOrRedirect::Template(Template::render("login", &context.into_json()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,44 +145,6 @@ pub struct ResetPasswordForm {
|
|||||||
pub new_password2: String,
|
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.
|
/// 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> {
|
pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), PeachWebError> {
|
||||||
info!(
|
info!(
|
||||||
@ -221,100 +166,63 @@ pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(),
|
|||||||
/// and is specifically for users who have forgotten their password.
|
/// and is specifically for users who have forgotten their password.
|
||||||
#[get("/reset_password")]
|
#[get("/reset_password")]
|
||||||
pub fn reset_password(flash: Option<FlashMessage>) -> Template {
|
pub fn reset_password(flash: Option<FlashMessage>) -> Template {
|
||||||
let mut context = ResetPasswordContext::build();
|
let mut context = Context::new();
|
||||||
context.back = Some("/".to_string());
|
context.insert("back", &Some("/".to_string()));
|
||||||
context.title = Some("Reset Password".to_string());
|
context.insert("title", &Some("Reset Password".to_string()));
|
||||||
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.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
|
/// 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.
|
/// and is specifically for users who have forgotten their password.
|
||||||
#[post("/reset_password", data = "<reset_password_form>")]
|
#[post("/reset_password", data = "<reset_password_form>")]
|
||||||
pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> Template {
|
pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> Template {
|
||||||
let result = save_reset_password_form(reset_password_form.into_inner());
|
let mut context = Context::new();
|
||||||
match result {
|
context.insert("back", &Some("/".to_string()));
|
||||||
Ok(_) => {
|
context.insert("title", &Some("Reset Password".to_string()));
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JSON password reset form request handler. This route is used by a user who is not logged in
|
let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) {
|
||||||
/// and is specifically for users who have forgotten their password.
|
Ok(_) => (
|
||||||
#[post("/reset_password", data = "<reset_password_form>")]
|
"success".to_string(),
|
||||||
pub fn reset_password_form_endpoint(reset_password_form: Json<ResetPasswordForm>) -> Value {
|
"New password is now saved. Return home to login".to_string(),
|
||||||
let result = save_reset_password_form(reset_password_form.into_inner());
|
),
|
||||||
match result {
|
Err(err) => (
|
||||||
Ok(_) => {
|
"error".to_string(),
|
||||||
let status = "success".to_string();
|
format!("Failed to reset password: {}", err),
|
||||||
let msg = "New password is now saved. Return home to login.".to_string();
|
),
|
||||||
build_json_response(status, None, Some(msg))
|
};
|
||||||
}
|
|
||||||
Err(err) => {
|
context.insert("flash_name", &Some(flash_name));
|
||||||
let status = "error".to_string();
|
context.insert("flash_msg", &Some(flash_msg));
|
||||||
let msg = format!("{}", err);
|
|
||||||
build_json_response(status, None, Some(msg))
|
Template::render("settings/admin/reset_password", &context.into_json())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /send_password_reset
|
// 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.
|
/// Page for users who have forgotten their password.
|
||||||
/// This route is used by a user who is not logged in
|
/// This route is used by a user who is not logged in
|
||||||
/// to initiate the sending of a new password reset.
|
/// to initiate the sending of a new password reset.
|
||||||
#[get("/forgot_password")]
|
#[get("/forgot_password")]
|
||||||
pub fn forgot_password_page(flash: Option<FlashMessage>) -> Template {
|
pub fn forgot_password_page(flash: Option<FlashMessage>) -> Template {
|
||||||
let mut context = SendPasswordResetContext::build();
|
let mut context = Context::new();
|
||||||
context.back = Some("/".to_string());
|
context.insert("back", &Some("/".to_string()));
|
||||||
context.title = Some("Send Password Reset".to_string());
|
context.insert("title", &Some("Send Password Reset".to_string()));
|
||||||
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_msg = Some(flash.message().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
|
/// Send password reset request handler. This route is used by a user who is not logged in
|
||||||
@ -323,27 +231,25 @@ pub fn forgot_password_page(flash: Option<FlashMessage>) -> Template {
|
|||||||
#[post("/send_password_reset")]
|
#[post("/send_password_reset")]
|
||||||
pub fn send_password_reset_post() -> Template {
|
pub fn send_password_reset_post() -> Template {
|
||||||
info!("++ send password reset post");
|
info!("++ send password reset post");
|
||||||
let result = password_utils::send_password_reset();
|
let mut context = Context::new();
|
||||||
match result {
|
context.insert("back", &Some("/".to_string()));
|
||||||
Ok(_) => {
|
context.insert("title", &Some("Send Password Reset".to_string()));
|
||||||
let mut context = ChangePasswordContext::build();
|
|
||||||
context.back = Some("/".to_string());
|
let (flash_name, flash_msg) = match password_utils::send_password_reset() {
|
||||||
context.title = Some("Send Password Reset".to_string());
|
Ok(_) => (
|
||||||
context.flash_name = Some("success".to_string());
|
"success".to_string(),
|
||||||
let flash_msg =
|
"A password reset link has been sent to the admin of this device".to_string(),
|
||||||
"A password reset link has been sent to the admin of this device".to_string();
|
),
|
||||||
context.flash_msg = Some(flash_msg);
|
Err(err) => (
|
||||||
Template::render("settings/admin/forgot_password", &context)
|
"error".to_string(),
|
||||||
}
|
format!("Failed to send password reset link: {}", err),
|
||||||
Err(err) => {
|
),
|
||||||
let mut context = ChangePasswordContext::build();
|
};
|
||||||
context.back = Some("/".to_string());
|
|
||||||
context.title = Some("Send Password Reset".to_string());
|
context.insert("flash_name", &Some(flash_name));
|
||||||
context.flash_name = Some("error".to_string());
|
context.insert("flash_msg", &Some(flash_msg));
|
||||||
context.flash_msg = Some(format!("Failed to send password reset link: {}", err));
|
|
||||||
Template::render("settings/admin/forgot_password", &context)
|
Template::render("settings/admin/forgot_password", &context.into_json())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /settings/change_password
|
// HELPERS AND ROUTES FOR /settings/change_password
|
||||||
@ -375,63 +281,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.
|
/// Change password request handler. This is used by a user who is already logged in.
|
||||||
#[get("/change_password")]
|
#[get("/change_password")]
|
||||||
pub fn change_password(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn change_password(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
let mut context = ChangePasswordContext::build();
|
let mut context = Context::new();
|
||||||
// set back icon link to network route
|
context.insert("back", &Some("/settings/admin".to_string()));
|
||||||
context.back = Some("/settings/admin".to_string());
|
context.insert("title", &Some("Change Password".to_string()));
|
||||||
context.title = Some("Change Password".to_string());
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_msg = Some(flash.message().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.
|
/// Change password form request handler. This route is used by a user who is already logged in.
|
||||||
#[post("/change_password", data = "<password_form>")]
|
#[post("/change_password", data = "<password_form>")]
|
||||||
pub fn change_password_post(password_form: Form<PasswordForm>, _auth: Authenticated) -> Template {
|
pub fn change_password_post(password_form: Form<PasswordForm>, _auth: Authenticated) -> Template {
|
||||||
let result = save_password_form(password_form.into_inner());
|
let mut context = Context::new();
|
||||||
match result {
|
context.insert("back", &Some("/settings/admin".to_string()));
|
||||||
Ok(_) => {
|
context.insert("title", &Some("Change Password".to_string()));
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JSON change password form request handler.
|
let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) {
|
||||||
#[post("/change_password", data = "<password_form>")]
|
Ok(_) => (
|
||||||
pub fn save_password_form_endpoint(
|
"success".to_string(),
|
||||||
password_form: Json<PasswordForm>,
|
"New password is now saved".to_string(),
|
||||||
_auth: Authenticated,
|
),
|
||||||
) -> Value {
|
Err(err) => (
|
||||||
let result = save_password_form(password_form.into_inner());
|
"error".to_string(),
|
||||||
match result {
|
format!("Failed to save new password: {}", err),
|
||||||
Ok(_) => {
|
),
|
||||||
let status = "success".to_string();
|
};
|
||||||
let msg = "Your password was successfully changed".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
context.insert("flash_name", &Some(flash_name));
|
||||||
}
|
context.insert("flash_msg", &Some(flash_msg));
|
||||||
Err(err) => {
|
|
||||||
let status = "error".to_string();
|
Template::render("settings/admin/change_password", &context.into_json())
|
||||||
let msg = format!("{}", err);
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,69 +1,37 @@
|
|||||||
use rocket::{get, request::FlashMessage};
|
use rocket::{get, request::FlashMessage, State};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::routes::authentication::Authenticated;
|
use crate::routes::authentication::Authenticated;
|
||||||
|
use crate::RocketConfig;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR / (HOME PAGE)
|
// 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("/")]
|
#[get("/")]
|
||||||
pub fn home(_auth: Authenticated) -> Template {
|
pub fn home(_auth: Authenticated, config: &State<RocketConfig>) -> Template {
|
||||||
let context = HomeContext {
|
let mut context = Context::new();
|
||||||
flash_name: None,
|
context.insert("flash_name", &None::<()>);
|
||||||
flash_msg: None,
|
context.insert("flash_msg", &None::<()>);
|
||||||
title: None,
|
context.insert("title", &None::<()>);
|
||||||
};
|
|
||||||
Template::render("home", &context)
|
// pass in mode from managed state so we can define appropriate urls in template
|
||||||
|
context.insert("standalone_mode", &config.standalone_mode);
|
||||||
|
|
||||||
|
Template::render("home", &context.into_json())
|
||||||
}
|
}
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /help
|
// 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")]
|
#[get("/help")]
|
||||||
pub fn help(flash: Option<FlashMessage>) -> Template {
|
pub fn help(flash: Option<FlashMessage>) -> Template {
|
||||||
let mut context = HelpContext::build();
|
let mut context = Context::new();
|
||||||
context.back = Some("/".to_string());
|
context.insert("back", &Some("/".to_string()));
|
||||||
context.title = Some("Help".to_string());
|
context.insert("title", &Some("Help".to_string()));
|
||||||
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.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::{
|
use rocket::{
|
||||||
form::{Form, FromForm},
|
form::{Form, FromForm},
|
||||||
get, post,
|
get, post,
|
||||||
request::FlashMessage,
|
request::FlashMessage,
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
|
serde::Deserialize,
|
||||||
uri,
|
uri,
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
|
||||||
use peach_lib::config_manager;
|
use peach_lib::config_manager;
|
||||||
use peach_lib::config_manager::load_peach_config;
|
|
||||||
|
|
||||||
use crate::error::PeachWebError;
|
use crate::error::PeachWebError;
|
||||||
use crate::routes::authentication::Authenticated;
|
use crate::routes::authentication::Authenticated;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /settings/admin
|
// 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.
|
/// Administrator settings menu.
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub fn admin_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn admin_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
let mut context = AdminMenuContext::build();
|
let mut context = Context::new();
|
||||||
// set back icon link to settings route
|
context.insert("back", &Some("/settings".to_string()));
|
||||||
context.back = Some("/settings".to_string());
|
context.insert("title", &Some("Administrator Settings".to_string()));
|
||||||
context.title = Some("Administrator Settings".to_string());
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.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
|
// 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.
|
/// View and delete currently configured admin.
|
||||||
#[get("/configure")]
|
#[get("/configure")]
|
||||||
pub fn configure_admin(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn configure_admin(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
let mut context = ConfigureAdminContext::build();
|
let mut context = Context::new();
|
||||||
// set back icon link to settings route
|
context.insert("back", &Some("/settings/admin".to_string()));
|
||||||
context.back = Some("/settings/admin".to_string());
|
context.insert("title", &Some("Configure Admin".to_string()));
|
||||||
context.title = Some("Configure Admin".to_string());
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.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
|
// HELPERS AND ROUTES FOR /settings/admin/add
|
||||||
@ -99,25 +73,6 @@ pub struct AddAdminForm {
|
|||||||
pub ssb_id: String,
|
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> {
|
pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> {
|
||||||
let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?;
|
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
|
// 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")]
|
#[get("/add")]
|
||||||
pub fn add_admin(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn add_admin(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
let mut context = AddAdminContext::build();
|
let mut context = Context::new();
|
||||||
context.back = Some("/settings/admin/configure".to_string());
|
context.insert("back", &Some("/settings/admin/configure".to_string()));
|
||||||
context.title = Some("Add Admin".to_string());
|
context.insert("title", &Some("Add Admin".to_string()));
|
||||||
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.flash_msg = Some(flash.message().to_string());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// template_dir is set in Rocket.toml
|
// 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>")]
|
#[post("/add", data = "<add_admin_form>")]
|
||||||
pub fn add_admin_post(add_admin_form: Form<AddAdminForm>, _auth: Authenticated) -> Flash<Redirect> {
|
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 result = save_add_admin_form(add_admin_form.into_inner());
|
||||||
let url = uri!(configure_admin);
|
let url = uri!("/settings/admin/configure");
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"),
|
Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"),
|
||||||
Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"),
|
Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"),
|
||||||
|
@ -3,27 +3,20 @@ use rocket::{
|
|||||||
form::{Form, FromForm},
|
form::{Form, FromForm},
|
||||||
get, post,
|
get, post,
|
||||||
request::FlashMessage,
|
request::FlashMessage,
|
||||||
serde::{
|
serde::Deserialize,
|
||||||
json::{Json, Value},
|
|
||||||
Deserialize, Serialize,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
use peach_lib::config_manager;
|
use peach_lib::{
|
||||||
use peach_lib::config_manager::load_peach_config;
|
config_manager, dyndns_client,
|
||||||
use peach_lib::dyndns_client;
|
error::PeachError,
|
||||||
use peach_lib::dyndns_client::{
|
jsonrpc_client_core::{Error, ErrorKind},
|
||||||
check_is_new_dyndns_domain, get_dyndns_subdomain, get_full_dynamic_domain,
|
jsonrpc_core::types::error::ErrorCode,
|
||||||
is_dns_updater_online,
|
|
||||||
};
|
};
|
||||||
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::{
|
||||||
use crate::routes::authentication::Authenticated;
|
context::dns::ConfigureDNSContext, error::PeachWebError, routes::authentication::Authenticated,
|
||||||
use crate::utils::build_json_response;
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, FromForm)]
|
#[derive(Debug, Deserialize, FromForm)]
|
||||||
pub struct DnsForm {
|
pub struct DnsForm {
|
||||||
@ -36,11 +29,12 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> {
|
|||||||
// first save local configurations
|
// first save local configurations
|
||||||
config_manager::set_external_domain(&dns_form.external_domain)?;
|
config_manager::set_external_domain(&dns_form.external_domain)?;
|
||||||
config_manager::set_dyndns_enabled_value(dns_form.enable_dyndns)?;
|
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 dynamic dns is enabled and this is a new domain name, then register it
|
||||||
if dns_form.enable_dyndns {
|
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
|
// 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 {
|
if is_new_domain {
|
||||||
match dyndns_client::register_domain(&full_dynamic_domain) {
|
match dyndns_client::register_domain(&full_dynamic_domain) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@ -52,24 +46,22 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> {
|
|||||||
info!("Failed to register dyndns domain: {:?}", err);
|
info!("Failed to register dyndns domain: {:?}", err);
|
||||||
// json response for failed update
|
// json response for failed update
|
||||||
let msg: String = match err {
|
let msg: String = match err {
|
||||||
PeachError::JsonRpcClientCore(source) => {
|
PeachError::JsonRpcClientCore(Error(
|
||||||
match source {
|
ErrorKind::JsonRpcError(err),
|
||||||
Error(ErrorKind::JsonRpcError(err), _state) => match err.code {
|
_state,
|
||||||
ErrorCode::ServerError(-32030) => {
|
)) => {
|
||||||
format!("Error registering domain: {} was previously registered", full_dynamic_domain)
|
if let ErrorCode::ServerError(-32030) = err.code {
|
||||||
}
|
format!(
|
||||||
_ => {
|
"Error registering domain: {} was previously registered",
|
||||||
format!("Failed to register dyndns domain {:?}", err)
|
full_dynamic_domain
|
||||||
}
|
)
|
||||||
},
|
} else {
|
||||||
_ => {
|
"Failed to register dyndns domain".to_string()
|
||||||
format!("Failed to register dyndns domain: {:?}", source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => "Failed to register dyndns domain".to_string(),
|
_ => "Failed to register dyndns domain".to_string(),
|
||||||
};
|
};
|
||||||
Err(PeachWebError::FailedToRegisterDynDomain { msg })
|
Err(PeachWebError::FailedToRegisterDynDomain(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,91 +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")]
|
#[get("/dns")]
|
||||||
pub fn configure_dns(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn configure_dns(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
let mut context = ConfigureDNSContext::build();
|
let mut context = ConfigureDNSContext::build();
|
||||||
|
|
||||||
// set back icon link to network route
|
// set back icon link to network route
|
||||||
context.back = Some("/settings/network".to_string());
|
context.back = Some("/settings/network".to_string());
|
||||||
context.title = Some("Configure DNS".to_string());
|
context.title = Some("Configure DNS".to_string());
|
||||||
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.message().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
|
|
||||||
Template::render("settings/network/configure_dns", &context)
|
Template::render("settings/network/configure_dns", &context)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/dns", data = "<dns>")]
|
#[post("/dns", data = "<dns>")]
|
||||||
pub fn configure_dns_post(dns: Form<DnsForm>, _auth: Authenticated) -> Template {
|
pub fn configure_dns_post(dns: Form<DnsForm>, _auth: Authenticated) -> Template {
|
||||||
let result = save_dns_configuration(dns.into_inner());
|
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 {
|
match result {
|
||||||
Ok(_) => {
|
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_name = Some("success".to_string());
|
||||||
context.flash_msg = Some("New dynamic dns configuration is now enabled".to_string());
|
context.flash_msg = Some("New dynamic dns configuration is now enabled".to_string());
|
||||||
Template::render("settings/network/configure_dns", &context)
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
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_name = Some("error".to_string());
|
||||||
context.flash_msg = Some(format!("Failed to save dns configurations: {}", err));
|
context.flash_msg = Some(format!("Failed to save dns configurations: {}", err));
|
||||||
Template::render("settings/network/configure_dns", &context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/dns/configure", data = "<dns_form>")]
|
Template::render("settings/network/configure_dns", &context)
|
||||||
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>, _auth: Authenticated) -> Value {
|
|
||||||
let result = save_dns_configuration(dns_form.into_inner());
|
|
||||||
match result {
|
|
||||||
Ok(_) => {
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = "New dynamic dns configuration is now enabled".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
let status = "error".to_string();
|
|
||||||
let msg = format!("{}", err);
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,22 @@
|
|||||||
use rocket::{get, request::FlashMessage, serde::Serialize};
|
use rocket::{get, request::FlashMessage};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
|
||||||
use crate::routes::authentication::Authenticated;
|
use crate::routes::authentication::Authenticated;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /settings
|
// 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.
|
/// View and delete currently configured admin.
|
||||||
#[get("/settings")]
|
#[get("/settings")]
|
||||||
pub fn settings_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn settings_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
let mut context = SettingsMenuContext::build();
|
let mut context = Context::new();
|
||||||
// set back icon link to network route
|
context.insert("back", &Some("/".to_string()));
|
||||||
context.back = Some("/".to_string());
|
context.insert("title", &Some("Settings".to_string()));
|
||||||
context.title = Some("Settings".to_string());
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.flash_msg = Some(flash.message().to_string());
|
|
||||||
};
|
};
|
||||||
Template::render("settings/menu", &context)
|
|
||||||
|
Template::render("settings/menu", &context.into_json())
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,41 +1,36 @@
|
|||||||
use rocket::{get, request::FlashMessage, serde::Serialize};
|
use rocket::{get, request::FlashMessage};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
|
||||||
use crate::routes::authentication::Authenticated;
|
use crate::routes::authentication::Authenticated;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /settings/scuttlebutt
|
// HELPERS AND ROUTES FOR /settings/scuttlebutt
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
pub struct ScuttlebuttSettingsContext {
|
|
||||||
pub back: Option<String>,
|
|
||||||
pub title: Option<String>,
|
|
||||||
pub flash_name: Option<String>,
|
|
||||||
pub flash_msg: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScuttlebuttSettingsContext {
|
|
||||||
pub fn build() -> ScuttlebuttSettingsContext {
|
|
||||||
ScuttlebuttSettingsContext {
|
|
||||||
back: None,
|
|
||||||
title: None,
|
|
||||||
flash_name: None,
|
|
||||||
flash_msg: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scuttlebutt settings menu.
|
/// Scuttlebutt settings menu.
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub fn ssb_settings_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn ssb_settings_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
let mut context = ScuttlebuttSettingsContext::build();
|
let mut context = Context::new();
|
||||||
// set back icon link to network route
|
context.insert("back", &Some("/settings".to_string()));
|
||||||
context.back = Some("/settings".to_string());
|
context.insert("title", &Some("Scuttlebutt Settings".to_string()));
|
||||||
context.title = Some("Scuttlebutt Settings".to_string());
|
|
||||||
// check to see if there is a flash message to display
|
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
context.flash_msg = Some(flash.message().to_string());
|
|
||||||
};
|
};
|
||||||
Template::render("settings/scuttlebutt", &context)
|
|
||||||
|
Template::render("settings/scuttlebutt/menu", &context.into_json())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sbot configuration page (includes form for updating configuration parameters).
|
||||||
|
#[get("/configure")]
|
||||||
|
pub fn configure_sbot(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("back", &Some("/settings/scuttlebutt".to_string()));
|
||||||
|
context.insert("title", &Some("Sbot Configuration".to_string()));
|
||||||
|
|
||||||
|
if let Some(flash) = flash {
|
||||||
|
context.insert("flash_name", &Some(flash.kind().to_string()));
|
||||||
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
Template::render("settings/scuttlebutt/configure_sbot", &context.into_json())
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use log::{debug, info, warn};
|
use log::info;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
get, post,
|
get,
|
||||||
request::FlashMessage,
|
request::FlashMessage,
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
serde::json::Value,
|
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -21,7 +20,6 @@ use peach_stats::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::routes::authentication::Authenticated;
|
use crate::routes::authentication::Authenticated;
|
||||||
use crate::utils::build_json_response;
|
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /status
|
// HELPERS AND ROUTES FOR /status
|
||||||
|
|
||||||
@ -119,16 +117,11 @@ impl StatusContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// test if go-sbot is running
|
// test if go-sbot is running
|
||||||
let sbot_is_online: bool;
|
|
||||||
let sbot_is_online_result = sbot_client::is_sbot_online();
|
let sbot_is_online_result = sbot_client::is_sbot_online();
|
||||||
match sbot_is_online_result {
|
let sbot_is_online: bool = match sbot_is_online_result {
|
||||||
Ok(val) => {
|
Ok(val) => val,
|
||||||
sbot_is_online = val;
|
Err(_err) => false,
|
||||||
}
|
};
|
||||||
Err(_err) => {
|
|
||||||
sbot_is_online = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusContext {
|
StatusContext {
|
||||||
back: None,
|
back: None,
|
||||||
@ -189,25 +182,6 @@ pub fn reboot_cmd(_auth: Authenticated) -> Flash<Redirect> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JSON request handler for device reboot.
|
|
||||||
#[post("/api/v1/admin/reboot")]
|
|
||||||
pub fn reboot_device(_auth: Authenticated) -> Value {
|
|
||||||
match reboot() {
|
|
||||||
Ok(_) => {
|
|
||||||
debug!("Going down for reboot...");
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = "Going down for reboot.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
warn!("Reboot failed");
|
|
||||||
let status = "error".to_string();
|
|
||||||
let msg = "Failed to reboot the device.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /power/shutdown
|
// HELPERS AND ROUTES FOR /power/shutdown
|
||||||
|
|
||||||
/// Executes a system command to shutdown the device immediately.
|
/// Executes a system command to shutdown the device immediately.
|
||||||
@ -227,25 +201,6 @@ pub fn shutdown_cmd(_auth: Authenticated) -> Flash<Redirect> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shutdown the device
|
|
||||||
#[post("/power/shutdown")]
|
|
||||||
pub fn shutdown_device(_auth: Authenticated) -> Value {
|
|
||||||
match shutdown() {
|
|
||||||
Ok(_) => {
|
|
||||||
debug!("Going down for shutdown...");
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = "Going down for shutdown.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
warn!("Shutdown failed");
|
|
||||||
let status = "error".to_string();
|
|
||||||
let msg = "Failed to shutdown the device.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /power
|
// HELPERS AND ROUTES FOR /power
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod ping;
|
pub mod scuttlebutt;
|
||||||
|
@ -1,162 +1,21 @@
|
|||||||
use rocket::{get, request::FlashMessage};
|
use rocket::{get, request::FlashMessage};
|
||||||
use rocket_dyn_templates::Template;
|
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;
|
use crate::routes::authentication::Authenticated;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /status/network
|
// 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")]
|
#[get("/network")]
|
||||||
pub fn network_status(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
pub fn network_status(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
|
||||||
// assign context through context_builder call
|
let mut context = NetworkStatusContext::build();
|
||||||
let mut context = NetworkContext::build();
|
|
||||||
context.back = Some("/status".to_string());
|
context.back = Some("/status".to_string());
|
||||||
context.title = Some("Network 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 {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
|
||||||
context.flash_name = Some(flash.kind().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.message().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
|
||||||
Template::render("status/network", &context)
|
Template::render("status/network", &context)
|
||||||
}
|
}
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
//! Helper routes for pinging services to check that they are active
|
|
||||||
use log::{debug, warn};
|
|
||||||
use rocket::get;
|
|
||||||
use rocket::serde::json::Value;
|
|
||||||
|
|
||||||
use peach_lib::network_client;
|
|
||||||
use peach_lib::oled_client;
|
|
||||||
use peach_lib::stats_client;
|
|
||||||
|
|
||||||
use crate::routes::authentication::Authenticated;
|
|
||||||
use crate::utils::build_json_response;
|
|
||||||
|
|
||||||
/// Status route: useful for checking connectivity from web client.
|
|
||||||
#[get("/ping")]
|
|
||||||
pub fn ping_pong(_auth: Authenticated) -> Value {
|
|
||||||
//pub fn ping_pong() -> Value {
|
|
||||||
// ping pong
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = "pong!".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Status route: check availability of `peach-network` microservice.
|
|
||||||
#[get("/ping/network")]
|
|
||||||
pub fn ping_network(_auth: Authenticated) -> Value {
|
|
||||||
match network_client::ping() {
|
|
||||||
Ok(_) => {
|
|
||||||
debug!("peach-network responded successfully");
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = "peach-network is available.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
warn!("peach-network failed to respond");
|
|
||||||
let status = "error".to_string();
|
|
||||||
let msg = "peach-network is unavailable.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Status route: check availability of `peach-oled` microservice.
|
|
||||||
#[get("/ping/oled")]
|
|
||||||
pub fn ping_oled(_auth: Authenticated) -> Value {
|
|
||||||
match oled_client::ping() {
|
|
||||||
Ok(_) => {
|
|
||||||
debug!("peach-oled responded successfully");
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = "peach-oled is available.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
warn!("peach-oled failed to respond");
|
|
||||||
let status = "error".to_string();
|
|
||||||
let msg = "peach-oled is unavailable.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Status route: check availability of `peach-stats` microservice.
|
|
||||||
#[get("/ping/stats")]
|
|
||||||
pub fn ping_stats(_auth: Authenticated) -> Value {
|
|
||||||
match stats_client::ping() {
|
|
||||||
Ok(_) => {
|
|
||||||
debug!("peach-stats responded successfully");
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = "peach-stats is available.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
warn!("peach-stats failed to respond");
|
|
||||||
let status = "error".to_string();
|
|
||||||
let msg = "peach-stats is unavailable.".to_string();
|
|
||||||
build_json_response(status, None, Some(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
16
peach-web/src/routes/status/scuttlebutt.rs
Normal file
16
peach-web/src/routes/status/scuttlebutt.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use rocket::get;
|
||||||
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
|
||||||
|
use crate::routes::authentication::Authenticated;
|
||||||
|
|
||||||
|
// HELPERS AND ROUTES FOR /status/scuttlebutt
|
||||||
|
|
||||||
|
#[get("/scuttlebutt")]
|
||||||
|
pub fn scuttlebutt_status(_auth: Authenticated) -> Template {
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("flash_name", &None::<()>);
|
||||||
|
context.insert("flash_msg", &None::<()>);
|
||||||
|
context.insert("title", &Some("Scuttlebutt Status"));
|
||||||
|
|
||||||
|
Template::render("status/scuttlebutt", &context.into_json())
|
||||||
|
}
|
@ -3,11 +3,8 @@ use std::io::Read;
|
|||||||
|
|
||||||
use rocket::http::{ContentType, Status};
|
use rocket::http::{ContentType, Status};
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket::serde::json::{json, Value};
|
|
||||||
use rocket::{Build, Config, Rocket};
|
use rocket::{Build, Config, Rocket};
|
||||||
|
|
||||||
use crate::utils::build_json_response;
|
|
||||||
|
|
||||||
use super::init_rocket;
|
use super::init_rocket;
|
||||||
|
|
||||||
// define authentication mode
|
// define authentication mode
|
||||||
@ -328,6 +325,13 @@ fn network_settings_menu_html() {
|
|||||||
assert!(body.contains("Network Configuration"));
|
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]
|
#[test]
|
||||||
fn deploy_ap() {
|
fn deploy_ap() {
|
||||||
let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
|
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);
|
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]
|
#[test]
|
||||||
fn dns_settings_html() {
|
fn dns_settings_html() {
|
||||||
let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
|
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"));
|
//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]
|
#[test]
|
||||||
fn add_ap_html() {
|
fn add_ap_html() {
|
||||||
let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
|
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("DOWNLOAD"));
|
||||||
assert!(body.contains("UPLOAD"));
|
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
|
// FILE TESTS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -3,26 +3,16 @@ pub mod monitor;
|
|||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
use rocket::response::{Redirect, Responder};
|
use rocket::response::{Redirect, Responder};
|
||||||
use rocket::serde::json::{Value, json};
|
use rocket::serde::Serialize;
|
||||||
use rocket::serde::{Serialize};
|
|
||||||
|
|
||||||
// HELPER FUNCTIONS
|
// HELPER FUNCTIONS
|
||||||
|
|
||||||
pub fn build_json_response(
|
|
||||||
status: String,
|
|
||||||
data: Option<Value>,
|
|
||||||
msg: Option<String>,
|
|
||||||
) -> Value {
|
|
||||||
json!({ "status": status, "data": data, "msg": msg })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct FlashContext {
|
pub struct FlashContext {
|
||||||
pub flash_name: Option<String>,
|
pub flash_name: Option<String>,
|
||||||
pub flash_msg: Option<String>,
|
pub flash_msg: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A helper enum which allows routes to either return a Template or a Redirect
|
/// A helper enum which allows routes to either return a Template or a Redirect
|
||||||
/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066
|
/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@ -30,4 +20,4 @@ pub struct FlashContext {
|
|||||||
pub enum TemplateOrRedirect {
|
pub enum TemplateOrRedirect {
|
||||||
Template(Template),
|
Template(Template),
|
||||||
Redirect(Redirect),
|
Redirect(Redirect),
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use nest::{Error, Store, Value};
|
use nest::{Error, Store, Value};
|
||||||
use rocket::form::{FromForm};
|
use rocket::form::FromForm;
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
@ -684,6 +684,7 @@ html {
|
|||||||
font-family: var(--sans-serif);
|
font-family: var(--sans-serif);
|
||||||
font-size: var(--font-size-7);
|
font-size: var(--font-size-7);
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-medium {
|
.label-medium {
|
||||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 9.9 KiB |
BIN
peach-web/static/icons/image-file.png
Normal file
BIN
peach-web/static/icons/image-file.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
peach-web/static/icons/orbits.png
Normal file
BIN
peach-web/static/icons/orbits.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
peach-web/static/icons/ram.png
Normal file
BIN
peach-web/static/icons/ram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `change_password.html.tera` template
|
|
||||||
|
|
||||||
- intercept button click for save (form submission of passwords)
|
|
||||||
- perform json api call
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
methods:
|
|
||||||
|
|
||||||
PEACH_AUTH.changePassword();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_AUTH = {};
|
|
||||||
|
|
||||||
// catch click of 'Save' button and make POST request
|
|
||||||
PEACH_AUTH.changePassword = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.body.addEventListener('submit', function(e) {
|
|
||||||
// prevent redirect on button press (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// capture form data
|
|
||||||
var formElement = document.querySelector("form");
|
|
||||||
// create form data object from the changePassword form element
|
|
||||||
var formData = new FormData(formElement);
|
|
||||||
var object = {};
|
|
||||||
// assign values from form
|
|
||||||
formData.forEach(function(value, key){
|
|
||||||
object[key] = value;
|
|
||||||
});
|
|
||||||
// perform json serialization
|
|
||||||
console.log(object);
|
|
||||||
var jsonData = JSON.stringify(object);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Saving new password.");
|
|
||||||
// send change_password POST request
|
|
||||||
fetch("/api/v1/admin/change_password", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var changePassInstance = PEACH_AUTH;
|
|
||||||
changePassInstance.changePassword();
|
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* Common javascript functions shared by multiple pages:
|
|
||||||
* - flashMsg
|
|
||||||
* - logout
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH = {};
|
|
||||||
|
|
||||||
// display a message by appending a paragraph element
|
|
||||||
PEACH.flashMsg = function(status, msg) {
|
|
||||||
// set the class of the element according to status
|
|
||||||
var elementClass;
|
|
||||||
if (status === "success") {
|
|
||||||
elementClass = "capsule center-text flash-message font-success";
|
|
||||||
} else if (status === "info") {
|
|
||||||
elementClass = "capsule center-text flash-message font-info";
|
|
||||||
} else {
|
|
||||||
elementClass = "capsule center-text flash-message font-failure";
|
|
||||||
};
|
|
||||||
|
|
||||||
var flashElement = document.getElementById("flashMsg");
|
|
||||||
// if flashElement exists, update the class & text
|
|
||||||
if (flashElement) {
|
|
||||||
flashElement.className = elementClass;
|
|
||||||
flashElement.innerText = msg;
|
|
||||||
// if flashElement does not exist, create it, set id, class, text & append
|
|
||||||
} else {
|
|
||||||
// create new div for flash message
|
|
||||||
var flashDiv = document.createElement("DIV");
|
|
||||||
// set div attributes
|
|
||||||
flashDiv.id = "flashMsg";
|
|
||||||
flashDiv.className = elementClass;
|
|
||||||
// add json response message to flash message div
|
|
||||||
var flashMsg = document.createTextNode(msg);
|
|
||||||
flashDiv.appendChild(flashMsg);
|
|
||||||
// insert the flash message div below the button div
|
|
||||||
var buttonDiv = document.getElementById("buttonDiv");
|
|
||||||
// flashDiv will be added to the end since buttonDiv is the last
|
|
||||||
// child within the parent element (card-container div)
|
|
||||||
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var commonInstance = PEACH;
|
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `configure_dns.html.tera` template,
|
|
||||||
corresponding to the web route `/settings/network/dns`
|
|
||||||
|
|
||||||
- intercept button click for save (form submission of dns settings)
|
|
||||||
- perform json api call
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_DNS = {};
|
|
||||||
|
|
||||||
// catch click of 'Add' button and make POST request
|
|
||||||
PEACH_DNS.configureDns = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.body.addEventListener('submit', function(e) {
|
|
||||||
// prevent redirect on button press (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// capture form data
|
|
||||||
var formElement = document.querySelector("form");
|
|
||||||
// create form data object from the configureDNS form element
|
|
||||||
var formData = new FormData(formElement);
|
|
||||||
var object = {};
|
|
||||||
// set checkbox to false (the value is only passed to formData if it is "on")
|
|
||||||
object["enable_dyndns"] = false;
|
|
||||||
// assign values from form
|
|
||||||
formData.forEach(function(value, key){
|
|
||||||
// convert checkbox to bool
|
|
||||||
if (key === "enable_dyndns") {
|
|
||||||
value = (value === "on");
|
|
||||||
}
|
|
||||||
object[key] = value;
|
|
||||||
});
|
|
||||||
// perform json serialization
|
|
||||||
console.log(object);
|
|
||||||
var jsonData = JSON.stringify(object);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Saving new DNS configurations");
|
|
||||||
// send add_wifi POST request
|
|
||||||
fetch("/api/v1/network/dns/configure", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
let statusIndicator = document.getElementById("dyndns-status-indicator");
|
|
||||||
// only remove the "dyndns-status-indicator" element if it exists
|
|
||||||
if (statusIndicator != null ) statusIndicator.remove();
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var configureDnsInstance = PEACH_DNS;
|
|
||||||
configureDnsInstance.configureDns();
|
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `network_add.html.tera` template,
|
|
||||||
corresponding to the web route `/network/wifi/add`
|
|
||||||
|
|
||||||
- intercept button click for add (form submission of credentials)
|
|
||||||
- perform json api call
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
methods:
|
|
||||||
|
|
||||||
PEACH_NETWORK.add();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_NETWORK = {};
|
|
||||||
|
|
||||||
// catch click of 'Add' button and make POST request
|
|
||||||
PEACH_NETWORK.add = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.body.addEventListener('submit', function(e) {
|
|
||||||
// prevent redirect on button press (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// capture form data
|
|
||||||
var formElement = document.querySelector("form");
|
|
||||||
// create form data object from the wifiCreds form element
|
|
||||||
var formData = new FormData(formElement);
|
|
||||||
var object = {};
|
|
||||||
// assign ssid and pass from form
|
|
||||||
formData.forEach(function(value, key){
|
|
||||||
object[key] = value;
|
|
||||||
});
|
|
||||||
// perform json serialization
|
|
||||||
var jsonData = JSON.stringify(object);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Adding WiFi credentials...");
|
|
||||||
// send add_wifi POST request
|
|
||||||
fetch("/api/v1/network/wifi", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var addInstance = PEACH_NETWORK;
|
|
||||||
addInstance.add();
|
|
@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `network_card.html.tera` template,
|
|
||||||
corresponding to the web route `/settings/network`
|
|
||||||
|
|
||||||
- intercept form submissions
|
|
||||||
- perform json api calls
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
methods:
|
|
||||||
|
|
||||||
PEACH_NETWORK.activateAp();
|
|
||||||
PEACH_NETWORK.activateClient();
|
|
||||||
PEACH_NETWORK.apMode();
|
|
||||||
PEACH_NETWORK.clientMode();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_NETWORK = {};
|
|
||||||
|
|
||||||
// catch click of 'Deploy Access Point' and make POST request
|
|
||||||
PEACH_NETWORK.activateAp = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var deployAP = document.getElementById('deployAccessPoint');
|
|
||||||
if (deployAP) {
|
|
||||||
deployAP.addEventListener('click', function(e) {
|
|
||||||
// prevent form submission (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Deploying access point...");
|
|
||||||
// send activate_ap POST request
|
|
||||||
fetch("/api/v1/network/activate_ap", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
console.log(jsonData.msg);
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
// if ap activation is successful, update the ui
|
|
||||||
if (jsonData.status === "success") {
|
|
||||||
PEACH_NETWORK.apMode();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch click of 'Enable WiFi' and make POST request
|
|
||||||
PEACH_NETWORK.activateClient = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var enableWifi = document.getElementById('connectWifi');
|
|
||||||
if (enableWifi) {
|
|
||||||
enableWifi.addEventListener('click', function(e) {
|
|
||||||
// prevent form submission (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Enabling WiFi client...");
|
|
||||||
// send activate_ap POST request
|
|
||||||
fetch("/api/v1/network/activate_client", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
console.log(jsonData.msg);
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
// if client activation is successful, update the ui
|
|
||||||
if (jsonData.status === "success") {
|
|
||||||
PEACH_NETWORK.clientMode();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace 'Deploy Access Point' button with 'Enable WiFi' button
|
|
||||||
PEACH_NETWORK.apMode = function() {
|
|
||||||
// create Enable WiFi button and add it to button div
|
|
||||||
var wifiButton = document.createElement("A");
|
|
||||||
wifiButton.className = "button center";
|
|
||||||
wifiButton.href = "/settings/network/wifi/activate";
|
|
||||||
wifiButton.id = "connectWifi";
|
|
||||||
var label = "Enable WiFi";
|
|
||||||
var buttonText = document.createTextNode(label);
|
|
||||||
wifiButton.appendChild(buttonText);
|
|
||||||
|
|
||||||
// append the new button to the buttons div
|
|
||||||
let buttons = document.getElementById("buttons");
|
|
||||||
buttons.appendChild(wifiButton);
|
|
||||||
|
|
||||||
// remove the old 'Deploy Access Point' button
|
|
||||||
let apButton = document.getElementById("deployAccessPoint");
|
|
||||||
apButton.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace 'Enable WiFi' button with 'Deploy Access Point' button
|
|
||||||
PEACH_NETWORK.clientMode = function() {
|
|
||||||
// create Deploy Access Point button and add it to button div
|
|
||||||
var apButton = document.createElement("A");
|
|
||||||
apButton.className = "button center";
|
|
||||||
apButton.href = "/settings/network/ap/activate";
|
|
||||||
apButton.id = "deployAccessPoint";
|
|
||||||
var label = "Deploy Access Point";
|
|
||||||
var buttonText = document.createTextNode(label);
|
|
||||||
apButton.appendChild(buttonText);
|
|
||||||
|
|
||||||
// append the new button to the buttons div
|
|
||||||
let buttons = document.getElementById("buttons");
|
|
||||||
buttons.appendChild(apButton);
|
|
||||||
|
|
||||||
// remove the old 'Enable Wifi' button
|
|
||||||
let wifiButton = document.getElementById("connectWifi");
|
|
||||||
wifiButton.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
var networkInstance = PEACH_NETWORK;
|
|
||||||
networkInstance.activateAp();
|
|
||||||
networkInstance.activateClient();
|
|
@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `network_detail.html.tera` template,
|
|
||||||
corresponding to the web route `/settings/network/wifi?<ssid>`
|
|
||||||
|
|
||||||
- intercept button clicks for connect, disconnect and forget
|
|
||||||
- perform json api call
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
methods:
|
|
||||||
|
|
||||||
PEACH_NETWORK.connect();
|
|
||||||
PEACH_NETWORK.disconnect();
|
|
||||||
PEACH_NETWORK.forget();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_NETWORK = {};
|
|
||||||
|
|
||||||
// catch click of 'Connect' button (form) and make POST request
|
|
||||||
PEACH_NETWORK.connect = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var connectWifi = document.getElementById('connectWifi');
|
|
||||||
if (connectWifi) {
|
|
||||||
connectWifi.addEventListener('click', function(e) {
|
|
||||||
// prevent form submission (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// retrieve ssid value and append to form data object
|
|
||||||
var ssid = document.getElementById('connectSsid').value;
|
|
||||||
// create key:value pair
|
|
||||||
var ssidData = { ssid: ssid };
|
|
||||||
// perform json serialization
|
|
||||||
var jsonData = JSON.stringify(ssidData);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Connecting to access point...");
|
|
||||||
// send add_wifi POST request
|
|
||||||
fetch("/api/v1/network/wifi/connect", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch click of 'Disconnect' button and make POST request
|
|
||||||
PEACH_NETWORK.disconnect = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var disconnectWifi = document.getElementById('disconnectWifi');
|
|
||||||
if (disconnectWifi) {
|
|
||||||
disconnectWifi.addEventListener('click', function(e) {
|
|
||||||
// prevent form submission (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// retrieve ssid value and append to form data object
|
|
||||||
var ssid = document.getElementById('disconnectSsid').value;
|
|
||||||
// create key:value pair
|
|
||||||
var ssidData = { ssid: ssid };
|
|
||||||
// perform json serialization
|
|
||||||
var jsonData = JSON.stringify(ssidData);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Disconnecting from access point...");
|
|
||||||
// send disconnect_wifi POST request
|
|
||||||
fetch("/api/v1/network/wifi/disconnect", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch click of 'Forget' button (form) and make POST request
|
|
||||||
PEACH_NETWORK.forget = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var forgetWifi = document.getElementById('forgetWifi');
|
|
||||||
if (forgetWifi) {
|
|
||||||
forgetWifi.addEventListener('click', function(e) {
|
|
||||||
// prevent form submission (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// retrieve ssid value
|
|
||||||
var ssid = document.getElementById('forgetSsid').value;
|
|
||||||
// create key:value pair
|
|
||||||
var ssidData = { ssid: ssid };
|
|
||||||
// perform json serialization
|
|
||||||
var jsonData = JSON.stringify(ssidData);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Removing credentials for access point...");
|
|
||||||
// send forget_ap POST request
|
|
||||||
fetch("/api/v1/network/wifi/forget", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var detailInstance = PEACH_NETWORK;
|
|
||||||
detailInstance.connect();
|
|
||||||
detailInstance.disconnect();
|
|
||||||
detailInstance.forget();
|
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `network_modify.html.tera` template
|
|
||||||
|
|
||||||
- intercept button click for modify (form submission of credentials)
|
|
||||||
- perform json api call
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
methods:
|
|
||||||
|
|
||||||
PEACH_NETWORK.modify();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_NETWORK = {};
|
|
||||||
|
|
||||||
// catch click of 'Save' button and make POST request
|
|
||||||
PEACH_NETWORK.modify = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.body.addEventListener('submit', function(e) {
|
|
||||||
// prevent redirect on button press (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// capture form data
|
|
||||||
var formElement = document.querySelector("form");
|
|
||||||
// create form data object from the wifiModify form element
|
|
||||||
var formData = new FormData(formElement);
|
|
||||||
var object = {};
|
|
||||||
// assign ssid and pass from form
|
|
||||||
formData.forEach(function(value, key){
|
|
||||||
object[key] = value;
|
|
||||||
});
|
|
||||||
// perform json serialization
|
|
||||||
var jsonData = JSON.stringify(object);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Updating WiFi password...");
|
|
||||||
// send new_password POST request
|
|
||||||
fetch("/api/v1/network/wifi/modify", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var modifyInstance = PEACH_NETWORK;
|
|
||||||
modifyInstance.modify();
|
|
@ -1,139 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `network_usage.html.tera` template,
|
|
||||||
corresponding to the web route `/settings/network/wifi/usage`
|
|
||||||
|
|
||||||
- intercept form submissions
|
|
||||||
- perform json api calls
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
methods:
|
|
||||||
|
|
||||||
PEACH_NETWORK.updateAlerts();
|
|
||||||
PEACH_NETWORK.resetUsage();
|
|
||||||
PEACH_NETWORK.toggleWarning();
|
|
||||||
PEACH_NETWORK.toggleCutoff();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_NETWORK = {};
|
|
||||||
|
|
||||||
// catch click of 'Update' and make POST request
|
|
||||||
PEACH_NETWORK.updateAlerts = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.body.addEventListener('submit', function(e) {
|
|
||||||
// prevent redirect on button press (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// capture form data
|
|
||||||
var formElement = document.querySelector("form");
|
|
||||||
let warn = formElement.elements.warn.value;
|
|
||||||
let cut = formElement.elements.cut.value;
|
|
||||||
let warn_flag = formElement.elements.warn_flag.checked;
|
|
||||||
let cut_flag = formElement.elements.cut_flag.checked;
|
|
||||||
// perform json serialization
|
|
||||||
var jsonData = JSON.stringify({
|
|
||||||
"warn": parseFloat(warn),
|
|
||||||
"cut": parseFloat(cut),
|
|
||||||
"warn_flag": warn_flag,
|
|
||||||
"cut_flag": cut_flag,
|
|
||||||
});
|
|
||||||
// send update_alerts POST request
|
|
||||||
fetch("/api/v1/network/wifi/usage", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch click of 'Reset' and make POST request
|
|
||||||
PEACH_NETWORK.resetUsage = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var resetBtn = document.getElementById('resetTotal');
|
|
||||||
if (resetBtn) {
|
|
||||||
resetBtn.addEventListener('click', function(e) {
|
|
||||||
// prevent form submission (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// send reset_data_usage POST request
|
|
||||||
fetch("/api/v1/network/wifi/usage/reset", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
console.log(jsonData.msg);
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
// if reset is successful, update the ui
|
|
||||||
if (jsonData.status === "success") {
|
|
||||||
console.log(jsonData.data);
|
|
||||||
PEACH_NETWORK.updateTotal(jsonData.data);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// update data usage total in ui
|
|
||||||
PEACH_NETWORK.updateTotal = function(data) {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
console.log(data);
|
|
||||||
let label = document.getElementById("dataTotal");
|
|
||||||
// take usage total as bytes, convert to MB and round to nearest integer
|
|
||||||
label.textContent = (data / 1024 / 1024).round();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// update ui for warning
|
|
||||||
PEACH_NETWORK.toggleWarning = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
let i = document.getElementById("warnIcon");
|
|
||||||
let warnCheck = document.getElementById("warnCheck");
|
|
||||||
warnCheck.addEventListener('click', function(e) {
|
|
||||||
console.log('Toggling warning icon state');
|
|
||||||
if (warnCheck.checked) {
|
|
||||||
i.className = "icon";
|
|
||||||
} else {
|
|
||||||
i.className = "icon icon-inactive";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// update ui for cutoff
|
|
||||||
PEACH_NETWORK.toggleCutoff = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
let i = document.getElementById("cutIcon");
|
|
||||||
let cutCheck = document.getElementById("cutCheck");
|
|
||||||
cutCheck.addEventListener('click', function(e) {
|
|
||||||
console.log('Toggling cutoff icon state');
|
|
||||||
if (cutCheck.checked) {
|
|
||||||
i.className = "icon";
|
|
||||||
} else {
|
|
||||||
i.className = "icon icon-inactive";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var usageInstance = PEACH_NETWORK;
|
|
||||||
usageInstance.resetUsage();
|
|
||||||
usageInstance.toggleWarning();
|
|
||||||
usageInstance.toggleCutoff();
|
|
||||||
usageInstance.updateAlerts();
|
|
@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
behavioural layer for the `power.html.tera` template,
|
|
||||||
corresponding to the web route `/power`
|
|
||||||
|
|
||||||
- intercept button clicks for reboot & shutdown
|
|
||||||
- perform json api calls
|
|
||||||
- update the dom
|
|
||||||
|
|
||||||
methods:
|
|
||||||
|
|
||||||
PEACH_DEVICE.reboot();
|
|
||||||
PEACH_DEVICE.shutdown();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_DEVICE = {};
|
|
||||||
|
|
||||||
// catch click of 'Reboot' button and make POST request
|
|
||||||
PEACH_DEVICE.reboot = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var rebootDevice = document.getElementById('rebootBtn');
|
|
||||||
if (rebootDevice) {
|
|
||||||
rebootDevice.addEventListener('click', function(e) {
|
|
||||||
// prevent redirect on button press (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// write reboot flash message
|
|
||||||
PEACH.flashMsg("success", "Rebooting the device...");
|
|
||||||
// send reboot_device POST request
|
|
||||||
fetch("/api/v1/admin/reboot", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
console.log(jsonData.msg);
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch click of 'Shutdown' button and make POST request
|
|
||||||
PEACH_DEVICE.shutdown = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var shutdownDevice = document.getElementById('shutdownBtn');
|
|
||||||
if (shutdownDevice) {
|
|
||||||
shutdownDevice.addEventListener('click', function(e) {
|
|
||||||
// prevent form submission (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// write shutdown flash message
|
|
||||||
PEACH.flashMsg("success", "Shutting down the device...");
|
|
||||||
// send shutdown_device POST request
|
|
||||||
fetch("/api/v1/shutdown", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
console.log(jsonData.msg);
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var deviceInstance = PEACH_DEVICE;
|
|
||||||
deviceInstance.reboot();
|
|
||||||
deviceInstance.shutdown();
|
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* behavioural layer for the `reset_password.html.tera` template,
|
|
||||||
*/
|
|
||||||
|
|
||||||
var PEACH_AUTH = {};
|
|
||||||
|
|
||||||
// catch click of 'Save' button and make POST request
|
|
||||||
PEACH_AUTH.resetPassword = function() {
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.body.addEventListener('submit', function(e) {
|
|
||||||
// prevent redirect on button press (default behavior)
|
|
||||||
e.preventDefault();
|
|
||||||
// capture form data
|
|
||||||
var formElement = document.querySelector("form");
|
|
||||||
// create form data object from the changePassword form element
|
|
||||||
var formData = new FormData(formElement);
|
|
||||||
var object = {};
|
|
||||||
// assign values from form
|
|
||||||
formData.forEach(function(value, key){
|
|
||||||
object[key] = value;
|
|
||||||
});
|
|
||||||
// perform json serialization
|
|
||||||
console.log(object);
|
|
||||||
var jsonData = JSON.stringify(object);
|
|
||||||
// write in-progress status message to ui
|
|
||||||
PEACH.flashMsg("info", "Saving new password.");
|
|
||||||
// send add_wifi POST request
|
|
||||||
fetch("/api/v1/admin/reset_password", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonData
|
|
||||||
})
|
|
||||||
.then( (response) => {
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.then( (jsonData) => {
|
|
||||||
// write json response message to ui
|
|
||||||
PEACH.flashMsg(jsonData.status, jsonData.msg);
|
|
||||||
})
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var resetPassInstance = PEACH_AUTH;
|
|
||||||
resetPassInstance.resetPassword();
|
|
@ -13,5 +13,4 @@
|
|||||||
<body>
|
<body>
|
||||||
{% block nav %}{% endblock nav %}
|
{% block nav %}{% endblock nav %}
|
||||||
</body>
|
</body>
|
||||||
<script type="text/javascript" src="/js/common.js"></script>
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -12,10 +12,6 @@
|
|||||||
<!-- display error message -->
|
<!-- display error message -->
|
||||||
<div class="center-text flash-message font-failure" style="padding-left: 5px;">{{ flash_msg }}.</div>
|
<div class="center-text flash-message font-failure" style="padding-left: 5px;">{{ flash_msg }}.</div>
|
||||||
{%- endif %}
|
{%- 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>
|
||||||
</div>
|
</div>
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -24,13 +24,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<!-- middle -->
|
<!-- middle -->
|
||||||
<a class="middle" href="/hello">
|
<a class="middle">
|
||||||
<div class="circle circle-large">
|
<div class="circle circle-large"></div>
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
<!-- bottom-left -->
|
<!-- bottom-left -->
|
||||||
<!-- SYSTEM STATUS LINK AND ICON -->
|
<!-- SYSTEM STATUS LINK AND ICON -->
|
||||||
|
{%- if standalone_mode == true -%}
|
||||||
|
<a class="bottom-left" href="/status/scuttlebutt" title="Status">
|
||||||
|
{% else -%}
|
||||||
<a class="bottom-left" href="/status" title="Status">
|
<a class="bottom-left" href="/status" title="Status">
|
||||||
|
{%- endif -%}
|
||||||
<div class="circle circle-small">
|
<div class="circle circle-small">
|
||||||
<img class="icon-medium" src="/icons/heart-pulse.svg">
|
<img class="icon-medium" src="/icons/heart-pulse.svg">
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,9 +10,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/power_menu.js"></script>
|
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -10,13 +10,8 @@
|
|||||||
<a class="button button-secondary center" href="/settings/admin/configure" title="Cancel">Cancel</a>
|
<a class="button button-secondary center" href="/settings/admin/configure" title="Cancel">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<!-- FLASH MESSAGE -->
|
||||||
<!-- FLASH MESSAGE -->
|
{% include "snippets/flash_message" %}
|
||||||
{% include "snippets/flash_message" %}
|
|
||||||
|
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<!-- CHANGE PASSWORD FORM -->
|
<!-- CHANGE PASSWORD FORM -->
|
||||||
<div class="card center">
|
<div class="card center">
|
||||||
<div class="form-container">
|
<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 for current password -->
|
||||||
<input id="currentPassword" class="center input" name="current_password" type="password" placeholder="Current password" title="Current password" autofocus>
|
<input id="currentPassword" class="center input" name="current_password" type="password" placeholder="Current password" title="Current password" autofocus>
|
||||||
<!-- input for new password -->
|
<!-- input for new password -->
|
||||||
@ -17,9 +17,6 @@
|
|||||||
</form>
|
</form>
|
||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/change_password.js"></script>
|
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -20,11 +20,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="button button-primary center full-width" style="margin-top: 25px;" href="/settings/admin/add" title="Add Admin">Add Admin</a>
|
<a class="button button-primary center full-width" style="margin-top: 25px;" href="/settings/admin/add" title="Add Admin">Add Admin</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
|
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
</div>
|
</div>
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -14,7 +14,5 @@
|
|||||||
</form>
|
</form>
|
||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
</div>
|
</div>
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -35,14 +35,8 @@
|
|||||||
<input id="changePasswordButton" class="button button-primary center" title="Add" type="submit" value="Save">
|
<input id="changePasswordButton" class="button button-primary center" title="Add" type="submit" value="Save">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
|
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/reset_password.js"></script>
|
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<!-- NETWORK ADD CREDENTIALS FORM -->
|
<!-- NETWORK ADD CREDENTIALS FORM -->
|
||||||
<div class="card center">
|
<div class="card center">
|
||||||
<div class="card-container">
|
<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 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 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 -->
|
<!-- input for network password -->
|
||||||
@ -15,9 +15,6 @@
|
|||||||
</form>
|
</form>
|
||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
|
|
||||||
{% include "snippets/noscript" %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/network_add.js"></script>
|
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -69,7 +69,6 @@
|
|||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/network_detail.js"></script>
|
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
{%- block card %}
|
{%- block card %}
|
||||||
<!-- CONFIGURE DNS FORM -->
|
<!-- CONFIGURE DNS FORM -->
|
||||||
<div class="card center">
|
<div class="card center">
|
||||||
|
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
|
|
||||||
{% if enable_dyndns %}
|
{% if enable_dyndns %}
|
||||||
<!-- DYNDNS STATUS INDICATOR -->
|
<!-- DYNDNS STATUS INDICATOR -->
|
||||||
<div id="dyndns-status-indicator" class="stack capsule{% if is_dyndns_online %} success-border{% else %} warning-border{% endif %}">
|
<div id="dyndns-status-indicator" class="stack capsule{% if is_dyndns_online %} success-border{% else %} warning-border{% endif %}">
|
||||||
@ -17,7 +15,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form id="configureDNS" action="/settings/network/dns" method="post">
|
<form id="configureDNS" action="/settings/network/dns" method="post">
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<!-- input for externaldomain -->
|
<!-- input for externaldomain -->
|
||||||
@ -25,7 +22,6 @@
|
|||||||
<label class="label-small input-label font-gray" for="external_domain" style="padding-top: 0.25rem;">External Domain (optional)</label>
|
<label class="label-small input-label font-gray" for="external_domain" style="padding-top: 0.25rem;">External Domain (optional)</label>
|
||||||
<input id="external_domain" class="form-input" style="margin-bottom: 0;"
|
<input id="external_domain" class="form-input" style="margin-bottom: 0;"
|
||||||
name="external_domain" type="text" title="external domain" value="{{ external_domain }}"></label>
|
name="external_domain" type="text" title="external domain" value="{{ external_domain }}"></label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<div>
|
<div>
|
||||||
@ -41,16 +37,13 @@
|
|||||||
<label id="cut" class="label-small input-label font-near-black">
|
<label id="cut" class="label-small input-label font-near-black">
|
||||||
<label class="label-small input-label font-gray" for="cut" style="padding-top: 0.25rem;">Dynamic DNS Domain</label>
|
<label class="label-small input-label font-gray" for="cut" style="padding-top: 0.25rem;">Dynamic DNS Domain</label>
|
||||||
<input id="dyndns_domain" class="alert-input" name="dynamic_domain" placeholder="" type="text" title="dyndns_domain" value="{{ dyndns_subdomain }}">.dyn.peachcloud.org</label>
|
<input id="dyndns_domain" class="alert-input" name="dynamic_domain" placeholder="" type="text" title="dyndns_domain" value="{{ dyndns_subdomain }}">.dyn.peachcloud.org</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="buttonDiv">
|
<div id="buttonDiv">
|
||||||
<input id="configureDNSButton" class="button button-primary center" title="Add" type="submit" value="Save">
|
<input id="configureDNSButton" class="button button-primary center" title="Add" type="submit" value="Save">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<!-- FLASH MESSAGE -->
|
||||||
<!-- FLASH MESSAGE -->
|
|
||||||
<!-- check for flash message and display accordingly -->
|
<!-- check for flash message and display accordingly -->
|
||||||
{% if flash_msg and flash_name == "success" %}
|
{% if flash_msg and flash_name == "success" %}
|
||||||
<!-- display success message -->
|
<!-- display success message -->
|
||||||
@ -62,14 +55,6 @@
|
|||||||
<!-- display error message -->
|
<!-- display error message -->
|
||||||
<div class="capsule center-text flash-message font-failure">{{ flash_msg }}.</div>
|
<div class="capsule center-text flash-message font-failure">{{ flash_msg }}.</div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/configure_dns.js"></script>
|
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
{%- extends "nav" -%}
|
{%- extends "nav" -%}
|
||||||
{%- block card -%}
|
{%- 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 -->
|
<!-- NETWORK DATA ALERTS VIEW -->
|
||||||
<form id="wifiAlerts" action="/settings/network/wifi/usage" class="card center" method="post">
|
<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="stack capsule" style="margin-left: 2rem; margin-right: 2rem;">
|
||||||
<div class="flex-grid">
|
<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>
|
<label class="label-small font-near-black">MB</label>
|
||||||
</div>
|
</div>
|
||||||
<label class="center-text label-small font-gray">USAGE TOTAL</label>
|
<label class="center-text label-small font-gray">USAGE TOTAL</label>
|
||||||
@ -43,5 +52,4 @@
|
|||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
</form>
|
</form>
|
||||||
<script type="text/javascript" src="/js/network_usage.js"></script>
|
|
||||||
{%- endblock card %}
|
{%- endblock card %}
|
||||||
|
@ -20,5 +20,4 @@
|
|||||||
<!-- FLASH MESSAGE -->
|
<!-- FLASH MESSAGE -->
|
||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/network_card.js"></script>
|
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -17,5 +17,4 @@
|
|||||||
{% include "snippets/flash_message" %}
|
{% include "snippets/flash_message" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/network_modify.js"></script>
|
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
{%- extends "nav" -%}
|
||||||
|
{%- block card %}
|
||||||
|
<!-- SBOT CONFIGURATION FORM -->
|
||||||
|
<div class="card center">
|
||||||
|
<form>
|
||||||
|
<div class="center" style="display: flex; flex-direction: column; width: 80%;" title="Number of hops to replicate">
|
||||||
|
<label for="hops" class="label-small">HOPS</label>
|
||||||
|
<div id="hops" style="display: flex; justify-content: space-evenly;">
|
||||||
|
<input type="radio" id="hops_0" name="hops" value="0">
|
||||||
|
<label for="hops_0">0</label>
|
||||||
|
<input type="radio" id="hops_1" name="hops" value="1">
|
||||||
|
<label for="hops_1">1</label>
|
||||||
|
<input type="radio" id="hops_2" name="hops" value="2">
|
||||||
|
<label for="hops_2">2</label>
|
||||||
|
<input type="radio" id="hops_3" name="hops" value="3">
|
||||||
|
<label for="hops_3">3</label>
|
||||||
|
<input type="radio" id="hops_4" name="hops" value="4">
|
||||||
|
<label for="hops_4">4</label><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="center" style="display: flex; justify-content: space-between; width: 80%;">
|
||||||
|
<div style="display: flex; flex-direction: column; width: 60%;" title="IP address on which the sbot runs">
|
||||||
|
<label for="ip" class="label-small">IP ADDRESS</label>
|
||||||
|
<input type="text" id="ip" name="ip">
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; width: 20%;" title="Port on which the sbot runs">
|
||||||
|
<label for="port" class="label-small">PORT</label>
|
||||||
|
<input type="text" id="port" name="port">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="center" style="display: flex; flex-direction: column; width: 80%;" title="Network key (aka 'caps key') to define the Scuttleverse in which the sbot operates in">
|
||||||
|
<label for="network_key" class="label-small">NETWORK KEY</label>
|
||||||
|
<input type="text" id="network_key" name="network_key">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="center" style="display: flex; flex-direction: column; width: 80%;" title="Directory in which the sbot database is saved">
|
||||||
|
<label for="database_dir" class="label-small">DATABASE DIRECTORY</label>
|
||||||
|
<input type="text" id="database_dir" name="database_dir">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="center" style="width: 80%;" title="Broadcast the IP and port of this sbot instance so that local peers can discovery it and attempt to connect">
|
||||||
|
<input type="checkbox" id="lan_broadcast" name="lan_broadcast">
|
||||||
|
<label for="lan_broadcast">Enable LAN Broadcasting</label><br>
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" id="lan_discovery" name="lan_discovery" title="Listen for the presence of local peers and attempt to connect if found">
|
||||||
|
<label for="lan_discovery">Enable LAN Discovery</label><br>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<input class="button button-primary center" type="button" value="Save">
|
||||||
|
<input class="button button-warning center" type="button" value="Restore Defaults">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{%- endblock card -%}
|
@ -4,15 +4,13 @@
|
|||||||
<div class="card center">
|
<div class="card center">
|
||||||
<!-- BUTTONS -->
|
<!-- BUTTONS -->
|
||||||
<div id="settingsButtons">
|
<div id="settingsButtons">
|
||||||
<a id="networkKey" class="button button-primary center" href="/settings/scuttlebutt/network_key" title="Set Network Key">Set Network Key</a>
|
<a id="configureSbot" class="button button-primary center" href="/settings/scuttlebutt/configure" title="Configure Sbot">Configure Sbot</a>
|
||||||
<a id="replicationHops" class="button button-primary center" href="/settings/scuttlebutt/hops" title="Set Replication Hops">Set Replication Hops</a>
|
|
||||||
<a id="removeFeeds" class="button button-primary center" href="/settings/scuttlebutt/remove_feeds" title="Remove Blocked Feeds">Remove Blocked Feeds</a>
|
|
||||||
<a id="setDirectory" class="button button-primary center" href="/settings/scuttlebutt/set_directory" title="Set Database Directory">Set Database Directory</a>
|
|
||||||
<a id="checkFilesystem" class="button button-primary center" href="/settings/scuttlebutt/check_fs" title="Check Filesystem">Check Filesystem</a>
|
|
||||||
<a id="repairFilesystem" class="button button-primary center" href="/settings/scuttlebutt/repair" title="Repair Filesystem">Repair Filesystem</a>
|
|
||||||
<a id="disable" class="button button-primary center" href="/settings/scuttlebutt/disable" title="Disable Sbot">Disable Sbot</a>
|
<a id="disable" class="button button-primary center" href="/settings/scuttlebutt/disable" title="Disable Sbot">Disable Sbot</a>
|
||||||
<a id="enable" class="button button-primary center" href="/settings/scuttlebutt/enable" title="Enable Sbot">Enable Sbot</a>
|
<a id="enable" class="button button-primary center" href="/settings/scuttlebutt/enable" title="Enable Sbot">Enable Sbot</a>
|
||||||
<a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot">Restart Sbot</a>
|
<a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot">Restart Sbot</a>
|
||||||
|
<a id="checkFilesystem" class="button button-primary center" href="/settings/scuttlebutt/check_fs" title="Check Filesystem">Check Filesystem</a>
|
||||||
|
<a id="repairFilesystem" class="button button-primary center" href="/settings/scuttlebutt/repair" title="Repair Filesystem">Repair Filesystem</a>
|
||||||
|
<a id="removeFeeds" class="button button-primary center" href="/settings/scuttlebutt/remove_feeds" title="Remove Blocked Feeds">Remove Blocked Feeds</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endblock card -%}
|
{%- endblock card -%}
|
@ -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>
|
|
@ -25,13 +25,13 @@
|
|||||||
{# Display microservice status for network, oled & stats #}
|
{# Display microservice status for network, oled & stats #}
|
||||||
<!-- PEACH-NETWORK STATUS STACK -->
|
<!-- PEACH-NETWORK STATUS STACK -->
|
||||||
<a class="link" href="/status/network">
|
<a class="link" href="/status/network">
|
||||||
<div class="stack capsule{% if network_ping == "ONLINE" %} success-border{% else %} warning-border{% endif %}">
|
<div class="stack capsule{% if network_ping == "ONLINE" %} success-border{% else %} warning-border{% endif %}">
|
||||||
<img id="networkIcon" class="icon{% if network_ping == "OFFLINE" %} icon-inactive{% endif %} icon-medium" alt="Network" title="Network microservice status" src="/icons/wifi.svg">
|
<img id="networkIcon" class="icon{% if network_ping == "OFFLINE" %} icon-inactive{% endif %} icon-medium" alt="Network" title="Network microservice status" src="/icons/wifi.svg">
|
||||||
<div class="stack" style="padding-top: 0.5rem;">
|
<div class="stack" style="padding-top: 0.5rem;">
|
||||||
<label class="label-small font-near-black">Networking</label>
|
<label class="label-small font-near-black">Networking</label>
|
||||||
<label class="label-small font-near-black">{{ network_ping }}</label>
|
<label class="label-small font-near-black">{{ network_ping }}</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
<!-- PEACH-OLED STATUS STACK -->
|
<!-- PEACH-OLED STATUS STACK -->
|
||||||
<div class="stack capsule{% if oled_ping == "ONLINE" %} success-border{% else %} warning-border{% endif %}">
|
<div class="stack capsule{% if oled_ping == "ONLINE" %} success-border{% else %} warning-border{% endif %}">
|
||||||
@ -67,14 +67,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- SBOT STATUS STACK -->
|
<!-- SBOT STATUS STACK -->
|
||||||
<div class="stack capsule{% if sbot_is_online %} success-border{% else %} warning-border{% endif %}">
|
<a class="link" href="/status/scuttlebutt">
|
||||||
<img id="networkIcon" class="icon{% if sbot_is_online != true %} icon-inactive{% endif %} icon-medium" alt="Sbot" title="Sbot status" src="/icons/hermies.svg">
|
<div class="stack capsule{% if sbot_is_online %} success-border{% else %} warning-border{% endif %}">
|
||||||
<div class="stack" style="padding-top: 0.5rem;">
|
<img id="networkIcon" class="icon{% if sbot_is_online != true %} icon-inactive{% endif %} icon-medium" alt="Sbot" title="Sbot status" src="/icons/hermies.svg">
|
||||||
<label class="label-small font-near-black">Sbot</label>
|
<div class="stack" style="padding-top: 0.5rem;">
|
||||||
<label class="label-small font-near-black">{% if sbot_is_online %} ONLINE {% else %} OFFLINE {% endif %} </label>
|
<label class="label-small font-near-black">Sbot</label>
|
||||||
|
<label class="label-small font-near-black">{% if sbot_is_online %} ONLINE {% else %} OFFLINE {% endif %} </label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
{# Display CPU usage meter #}
|
{# Display CPU usage meter #}
|
||||||
{%- if cpu_stat_percent -%}
|
{%- if cpu_stat_percent -%}
|
||||||
|
87
peach-web/templates/status/scuttlebutt.html.tera
Normal file
87
peach-web/templates/status/scuttlebutt.html.tera
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{%- extends "nav" -%}
|
||||||
|
{%- block card %}
|
||||||
|
<!-- SCUTTLEBUTT STATUS -->
|
||||||
|
<div class="card center">
|
||||||
|
<!-- NETWORK INFO BOX -->
|
||||||
|
<div class="capsule capsule-container success-border">
|
||||||
|
<!-- NETWORK STATUS GRID -->
|
||||||
|
<div class="two-grid" title="PeachCloud network mode and status">
|
||||||
|
<!-- top-right config icon -->
|
||||||
|
<a class="link two-grid-top-right" href="/settings/scuttlebutt" title="Configure Scuttlebutt settings">
|
||||||
|
<img id="configureNetworking" class="icon-small" src="/icons/cog.svg" alt="Configure">
|
||||||
|
</a>
|
||||||
|
<!-- left column -->
|
||||||
|
<!-- network mode icon with label -->
|
||||||
|
<div class="grid-column-1">
|
||||||
|
<img id="netModeIcon" class="center icon icon-active" src="/icons/hermies.svg" alt="WiFi router">
|
||||||
|
<label id="netModeLabel" for="netModeIcon" class="center label-small font-gray" style="margin-top: 0.5rem;" title="Access Point Online">ACTIVE</label>
|
||||||
|
</div>
|
||||||
|
<!-- right column -->
|
||||||
|
<!-- network mode, ssid & ip with labels -->
|
||||||
|
<div class="grid-column-2">
|
||||||
|
<label class="label-small font-gray" for="netMode" title="Network Mode">VERSION</label>
|
||||||
|
<p id="netMode" class="card-text" title="Network Mode">1.1.0-alpha</p>
|
||||||
|
<label class="label-small font-gray" for="netSsid" title="Access Point SSID" style="margin-top: 0.5rem;">UPTIME</label>
|
||||||
|
<p id="netSsid" class="card-text" title="SSID">3 days & 7 hours</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="middleSection" style="margin-top: 1rem;">
|
||||||
|
<div id="ssbSocialCounts" class="center" style="display: flex; justify-content: space-between; width: 90%;">
|
||||||
|
<div style="display: flex; align-items: last baseline;">
|
||||||
|
<label class="card-text" style="margin-right: 2px;">21</label>
|
||||||
|
<label class="label-small font-gray">Friends</label>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: last baseline;">
|
||||||
|
<label class="card-text" style="margin-right: 2px;">5</label>
|
||||||
|
<label class="label-small font-gray">Follows</label>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: last baseline;">
|
||||||
|
<label class="card-text" style="margin-right: 2px;">38</label>
|
||||||
|
<label class="label-small font-gray">Followers</label>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: last baseline;">
|
||||||
|
<label class="card-text" style="margin-right: 2px;">2</label>
|
||||||
|
<label class="label-small font-gray">Blocks</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
<p class="card-text">Latest sequence number: </p>
|
||||||
|
<p class="card-text">Network key: </p>
|
||||||
|
<p>Process uptime: </p>
|
||||||
|
<p>Sbot version: </p>
|
||||||
|
<p>Replication hops: </p>
|
||||||
|
<p>Process status: </p>
|
||||||
|
<p>Blobstore size: </p>
|
||||||
|
<p>Last time you visited this page, latest sequence was x ... now it's y</p>
|
||||||
|
<p>Number of follows / followers / friends / blocks</p>
|
||||||
|
-->
|
||||||
|
<!-- THREE-ACROSS STACK -->
|
||||||
|
<div class="three-grid card-container" style="margin-top: 1rem;">
|
||||||
|
<div class="stack">
|
||||||
|
<img class="icon" title="Hops" src="/icons/orbits.png">
|
||||||
|
<div class="flex-grid" style="padding-top: 0.5rem;">
|
||||||
|
<label class="label-medium" style="padding-right: 3px;" title="Replication hops">2</label>
|
||||||
|
</div>
|
||||||
|
<label class="label-small font-gray">HOPS</label>
|
||||||
|
</div>
|
||||||
|
<div class="stack">
|
||||||
|
<img class="icon" title="Blobs" src="/icons/image-file.png">
|
||||||
|
<div class="flex-grid" style="padding-top: 0.5rem;">
|
||||||
|
<label class="label-medium" style="padding-right: 3px;" title="Blobstore size in MB">163</label>
|
||||||
|
<label class="label-small font-near-black">MB</label>
|
||||||
|
</div>
|
||||||
|
<label class="label-small font-gray">BLOBSTORE</label>
|
||||||
|
</div>
|
||||||
|
<div class="stack">
|
||||||
|
<img class="icon" title="Memory" src="/icons/ram.png">
|
||||||
|
<div class="flex-grid" style="padding-top: 0.5rem;">
|
||||||
|
<label class="label-medium" style="padding-right: 3px;" title="Memory usage of the go-sbot process in MB">47</label>
|
||||||
|
<label class="label-small font-near-black">MB</label>
|
||||||
|
</div>
|
||||||
|
<label class="label-small font-gray">MEMORY</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endblock card -%}
|
Reference in New Issue
Block a user