Merge pull request 'Refactor routes into functional groupings' (#12) from routes-refactor into main
Reviewed-on: #12
This commit is contained in:
commit
1aa5821adf
539
Cargo.lock
generated
539
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
1460
peach-buttons/Cargo.lock
generated
1460
peach-buttons/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
1603
peach-oled/Cargo.lock
generated
1603
peach-oled/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,105 +0,0 @@
|
||||
//! This module contains core api functions shared by json_api.rs and by routes.rs
|
||||
//!
|
||||
//! These functions return Results which are then handled by the json api or the html routes
|
||||
//! and turned into a rocket response appropriately.
|
||||
use log::info;
|
||||
|
||||
use crate::error::PeachWebError;
|
||||
use crate::forms::{AddAdminForm, DnsForm, PasswordForm, ResetPasswordForm};
|
||||
use peach_lib::config_manager;
|
||||
use peach_lib::dyndns_client;
|
||||
use peach_lib::dyndns_client::{check_is_new_dyndns_domain, get_full_dynamic_domain};
|
||||
use peach_lib::error::PeachError;
|
||||
use peach_lib::jsonrpc_client_core::{Error, ErrorKind};
|
||||
use peach_lib::jsonrpc_core::types::error::ErrorCode;
|
||||
use peach_lib::password_utils;
|
||||
|
||||
pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> {
|
||||
// first save local configurations
|
||||
config_manager::set_external_domain(&dns_form.external_domain)?;
|
||||
config_manager::set_dyndns_enabled_value(dns_form.enable_dyndns)?;
|
||||
// if dynamic dns is enabled and this is a new domain name, then register it
|
||||
if dns_form.enable_dyndns {
|
||||
let full_dynamic_domain = get_full_dynamic_domain(&dns_form.dynamic_domain);
|
||||
// check if this is a new domain or if its already registered
|
||||
let is_new_domain = check_is_new_dyndns_domain(&full_dynamic_domain);
|
||||
if is_new_domain {
|
||||
match dyndns_client::register_domain(&full_dynamic_domain) {
|
||||
Ok(_) => {
|
||||
info!("Registered new dyndns domain");
|
||||
// successful update
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Failed to register dyndns domain: {:?}", err);
|
||||
// json response for failed update
|
||||
let msg: String = match err {
|
||||
PeachError::JsonRpcClientCore { source } => {
|
||||
match source {
|
||||
Error(ErrorKind::JsonRpcError(err), _state) => match err.code {
|
||||
ErrorCode::ServerError(-32030) => {
|
||||
format!("Error registering domain: {} was previously registered", full_dynamic_domain)
|
||||
}
|
||||
_ => {
|
||||
format!("Failed to register dyndns domain {:?}", err)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
format!("Failed to register dyndns domain: {:?}", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => "Failed to register dyndns domain".to_string(),
|
||||
};
|
||||
Err(PeachWebError::FailedToRegisterDynDomain { msg })
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the domain is already registered, then dont re-register, and just return success
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// this function is for use by a user who is already logged in to change their password
|
||||
pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> {
|
||||
info!(
|
||||
"change password!: {} {} {}",
|
||||
password_form.old_password, password_form.new_password1, password_form.new_password2
|
||||
);
|
||||
password_utils::verify_password(&password_form.old_password)?;
|
||||
// if the previous line did not throw an error, then the old password is correct
|
||||
password_utils::validate_new_passwords(
|
||||
&password_form.new_password1,
|
||||
&password_form.new_password2,
|
||||
)?;
|
||||
// if the previous line did not throw an error, then the new password is valid
|
||||
password_utils::set_new_password(&password_form.new_password1)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// this function is publicly exposed for users who have forgotten their password
|
||||
pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), PeachWebError> {
|
||||
info!(
|
||||
"reset password!: {} {} {}",
|
||||
password_form.temporary_password, password_form.new_password1, password_form.new_password2
|
||||
);
|
||||
password_utils::verify_temporary_password(&password_form.temporary_password)?;
|
||||
// if the previous line did not throw an error, then the secret_link is correct
|
||||
password_utils::validate_new_passwords(
|
||||
&password_form.new_password1,
|
||||
&password_form.new_password2,
|
||||
)?;
|
||||
// if the previous line did not throw an error, then the new password is valid
|
||||
password_utils::set_new_password(&password_form.new_password1)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> {
|
||||
let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?;
|
||||
// if the previous line didn't throw an error then it was a success
|
||||
Ok(())
|
||||
}
|
@ -1,890 +0,0 @@
|
||||
//! Build context objects for inclusion in Tera templates.
|
||||
//!
|
||||
//! Each context object is represented by a struct which implements a build
|
||||
//! method. Context objects provide the means by which application and device
|
||||
//! state are made available for rendering in the templates.
|
||||
|
||||
// Context object struct names:
|
||||
//
|
||||
// DeviceContext
|
||||
// ErrorContext
|
||||
// FlashContext
|
||||
// HelpContext
|
||||
// HomeContext
|
||||
// LoginContext
|
||||
// MessageContext
|
||||
// NetworkContext
|
||||
// NetworkAddContext
|
||||
// NetworkAlertContext
|
||||
// NetworkDetailContext
|
||||
// NetworkListContext
|
||||
// PeerContext
|
||||
// ProfileContext
|
||||
// ShutdownContext
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use peach_lib::config_manager::load_peach_config;
|
||||
use peach_lib::dyndns_client;
|
||||
use peach_lib::dyndns_client::{get_dyndns_subdomain, is_dns_updater_online};
|
||||
use peach_lib::network_client;
|
||||
use peach_lib::network_client::{AccessPoint, Networks, Scan};
|
||||
use peach_lib::oled_client;
|
||||
use peach_lib::sbot_client;
|
||||
use peach_lib::stats_client;
|
||||
use peach_lib::stats_client::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat, Traffic};
|
||||
|
||||
use crate::monitor;
|
||||
use crate::monitor::{Alert, Data, Threshold};
|
||||
|
||||
// used in /device for system statistics
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct DeviceContext {
|
||||
pub back: Option<String>,
|
||||
pub cpu_stat_percent: Option<CpuStatPercentages>,
|
||||
pub disk_stats: Vec<DiskUsage>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub load_average: Option<LoadAverage>,
|
||||
pub mem_stats: Option<MemStat>,
|
||||
pub network_ping: String,
|
||||
pub oled_ping: String,
|
||||
pub stats_ping: String,
|
||||
pub dyndns_enabled: bool,
|
||||
pub dyndns_is_online: bool,
|
||||
pub config_is_valid: bool,
|
||||
pub sbot_is_online: bool,
|
||||
pub title: Option<String>,
|
||||
pub uptime: Option<i32>,
|
||||
}
|
||||
|
||||
impl DeviceContext {
|
||||
pub fn build() -> DeviceContext {
|
||||
// convert result to Option<CpuStatPercentages>, discard any error
|
||||
let cpu_stat_percent = stats_client::cpu_stats_percent().ok();
|
||||
let load_average = stats_client::load_average().ok();
|
||||
let mem_stats = stats_client::mem_stats().ok();
|
||||
let network_ping = match network_client::ping() {
|
||||
Ok(_) => "ONLINE".to_string(),
|
||||
Err(_) => "OFFLINE".to_string(),
|
||||
};
|
||||
let oled_ping = match oled_client::ping() {
|
||||
Ok(_) => "ONLINE".to_string(),
|
||||
Err(_) => "OFFLINE".to_string(),
|
||||
};
|
||||
let stats_ping = match stats_client::ping() {
|
||||
Ok(_) => "ONLINE".to_string(),
|
||||
Err(_) => "OFFLINE".to_string(),
|
||||
};
|
||||
let uptime = match stats_client::uptime() {
|
||||
Ok(mins) => mins,
|
||||
Err(_) => "Unavailable".to_string(),
|
||||
};
|
||||
|
||||
// serialize disk usage data into Vec<DiskUsage>
|
||||
let disk_usage_stats = match stats_client::disk_usage() {
|
||||
Ok(disks) => {
|
||||
let partitions: Vec<DiskUsage> = serde_json::from_str(disks.as_str())
|
||||
.expect("Failed to deserialize disk_usage response");
|
||||
partitions
|
||||
}
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
|
||||
let mut disk_stats = Vec::new();
|
||||
// select only the partition we're interested in: /dev/mmcblk0p2 ("/")
|
||||
for disk in disk_usage_stats {
|
||||
if disk.mountpoint == "/" {
|
||||
disk_stats.push(disk);
|
||||
}
|
||||
}
|
||||
|
||||
// parse the uptime string to a signed integer (for math)
|
||||
let uptime_parsed = uptime.parse::<i32>().ok();
|
||||
|
||||
// dyndns_is_online & config_is_valid
|
||||
let dyndns_enabled: bool;
|
||||
let dyndns_is_online: bool;
|
||||
let config_is_valid: bool;
|
||||
let load_peach_config_result = load_peach_config();
|
||||
match load_peach_config_result {
|
||||
Ok(peach_config) => {
|
||||
dyndns_enabled = peach_config.dyn_enabled;
|
||||
config_is_valid = true;
|
||||
if dyndns_enabled {
|
||||
let is_dyndns_online_result = dyndns_client::is_dns_updater_online();
|
||||
match is_dyndns_online_result {
|
||||
Ok(is_online) => {
|
||||
dyndns_is_online = is_online;
|
||||
}
|
||||
Err(_err) => {
|
||||
dyndns_is_online = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dyndns_is_online = false;
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
dyndns_enabled = false;
|
||||
dyndns_is_online = false;
|
||||
config_is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// test if go-sbot is running
|
||||
let sbot_is_online: bool;
|
||||
let sbot_is_online_result = sbot_client::is_sbot_online();
|
||||
match sbot_is_online_result {
|
||||
Ok(val) => {
|
||||
sbot_is_online = val;
|
||||
}
|
||||
Err(_err) => {
|
||||
sbot_is_online = false;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceContext {
|
||||
back: None,
|
||||
cpu_stat_percent,
|
||||
disk_stats,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
load_average,
|
||||
mem_stats,
|
||||
network_ping,
|
||||
oled_ping,
|
||||
stats_ping,
|
||||
dyndns_enabled,
|
||||
dyndns_is_online,
|
||||
config_is_valid,
|
||||
sbot_is_online,
|
||||
title: None,
|
||||
uptime: uptime_parsed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ErrorContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl ErrorContext {
|
||||
pub fn build() -> ErrorContext {
|
||||
ErrorContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FlashContext {
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MessageContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl MessageContext {
|
||||
pub fn build() -> MessageContext {
|
||||
MessageContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkContext {
|
||||
pub ap_ip: String,
|
||||
pub ap_ssid: String,
|
||||
pub ap_state: String,
|
||||
pub ap_traffic: Option<Traffic>,
|
||||
pub wlan_ip: String,
|
||||
pub wlan_rssi: Option<String>,
|
||||
pub wlan_scan: Option<Vec<Scan>>,
|
||||
pub wlan_ssid: String,
|
||||
pub wlan_state: String,
|
||||
pub wlan_status: String,
|
||||
pub wlan_traffic: Option<Traffic>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
// allows for passing in the ssid of a chosen access point
|
||||
// this is used in the network_detail template
|
||||
pub selected: Option<String>,
|
||||
// page title for header in navbar
|
||||
pub title: Option<String>,
|
||||
// url for back-arrow link
|
||||
pub back: Option<String>,
|
||||
}
|
||||
|
||||
impl NetworkContext {
|
||||
pub fn build() -> NetworkContext {
|
||||
let ap_ip = match network_client::ip("ap0") {
|
||||
Ok(ip) => ip,
|
||||
Err(_) => "x.x.x.x".to_string(),
|
||||
};
|
||||
let ap_ssid = match network_client::ssid("ap0") {
|
||||
Ok(ssid) => ssid,
|
||||
Err(_) => "Not currently activated".to_string(),
|
||||
};
|
||||
let ap_state = match network_client::state("ap0") {
|
||||
Ok(state) => state,
|
||||
Err(_) => "Interface unavailable".to_string(),
|
||||
};
|
||||
let ap_traffic = match network_client::traffic("ap0") {
|
||||
Ok(traffic) => {
|
||||
let mut t = traffic;
|
||||
// modify traffic values & assign measurement unit
|
||||
// based on received and transmitted values
|
||||
// if received > 999 MB, convert it to GB
|
||||
if t.received > 1_047_527_424 {
|
||||
t.received /= 1_073_741_824;
|
||||
t.rx_unit = Some("GB".to_string());
|
||||
} else if t.received > 0 {
|
||||
// otherwise, convert it to MB
|
||||
t.received = (t.received / 1024) / 1024;
|
||||
t.rx_unit = Some("MB".to_string());
|
||||
} else {
|
||||
t.received = 0;
|
||||
t.rx_unit = Some("MB".to_string());
|
||||
}
|
||||
|
||||
if t.transmitted > 1_047_527_424 {
|
||||
t.transmitted /= 1_073_741_824;
|
||||
t.tx_unit = Some("GB".to_string());
|
||||
} else if t.transmitted > 0 {
|
||||
t.transmitted = (t.transmitted / 1024) / 1024;
|
||||
t.tx_unit = Some("MB".to_string());
|
||||
} else {
|
||||
t.transmitted = 0;
|
||||
t.tx_unit = Some("MB".to_string());
|
||||
}
|
||||
Some(t)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
let wlan_ip = match network_client::ip("wlan0") {
|
||||
Ok(ip) => ip,
|
||||
Err(_) => "x.x.x.x".to_string(),
|
||||
};
|
||||
let wlan_rssi = match network_client::rssi_percent("wlan0") {
|
||||
Ok(rssi) => Some(rssi),
|
||||
Err(_) => None,
|
||||
};
|
||||
let wlan_scan = match network_client::available_networks("wlan0") {
|
||||
Ok(networks) => {
|
||||
let scan: Vec<Scan> = serde_json::from_str(networks.as_str())
|
||||
.expect("Failed to deserialize scan_networks response");
|
||||
Some(scan)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
let wlan_ssid = match network_client::ssid("wlan0") {
|
||||
Ok(ssid) => ssid,
|
||||
Err(_) => "Not connected".to_string(),
|
||||
};
|
||||
let wlan_state = match network_client::state("wlan0") {
|
||||
Ok(state) => state,
|
||||
Err(_) => "Interface unavailable".to_string(),
|
||||
};
|
||||
let wlan_status = match network_client::status("wlan0") {
|
||||
Ok(status) => status,
|
||||
Err(_) => "Interface unavailable".to_string(),
|
||||
};
|
||||
let wlan_traffic = match network_client::traffic("wlan0") {
|
||||
Ok(traffic) => {
|
||||
let mut t = traffic;
|
||||
// modify traffic values & assign measurement unit
|
||||
// based on received and transmitted values
|
||||
// if received > 999 MB, convert it to GB
|
||||
if t.received > 1_047_527_424 {
|
||||
t.received /= 1_073_741_824;
|
||||
t.rx_unit = Some("GB".to_string());
|
||||
} else if t.received > 0 {
|
||||
// otherwise, convert it to MB
|
||||
t.received = (t.received / 1024) / 1024;
|
||||
t.rx_unit = Some("MB".to_string());
|
||||
} else {
|
||||
t.received = 0;
|
||||
t.rx_unit = Some("MB".to_string());
|
||||
}
|
||||
|
||||
if t.transmitted > 1_047_527_424 {
|
||||
t.transmitted /= 1_073_741_824;
|
||||
t.tx_unit = Some("GB".to_string());
|
||||
} else if t.transmitted > 0 {
|
||||
t.transmitted = (t.transmitted / 1024) / 1024;
|
||||
t.tx_unit = Some("MB".to_string());
|
||||
} else {
|
||||
t.transmitted = 0;
|
||||
t.tx_unit = Some("MB".to_string());
|
||||
}
|
||||
Some(t)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
NetworkContext {
|
||||
ap_ip,
|
||||
ap_ssid,
|
||||
ap_state,
|
||||
ap_traffic,
|
||||
wlan_ip,
|
||||
wlan_rssi,
|
||||
wlan_scan,
|
||||
wlan_ssid,
|
||||
wlan_state,
|
||||
wlan_status,
|
||||
wlan_traffic,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
selected: None,
|
||||
title: None,
|
||||
back: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used in /network/wifi/add?<ssid>
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkAddContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub selected: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl NetworkAddContext {
|
||||
pub fn build() -> NetworkAddContext {
|
||||
NetworkAddContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
selected: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used in /network/wifi/alert for traffic alerts
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkAlertContext {
|
||||
pub alert: Alert,
|
||||
pub back: Option<String>,
|
||||
pub data_total: Data, // combined stored and current wifi traffic in bytes
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub threshold: Threshold,
|
||||
pub title: Option<String>,
|
||||
pub traffic: Traffic, // current wifi traffic in bytes (since boot)
|
||||
}
|
||||
|
||||
impl NetworkAlertContext {
|
||||
pub fn build() -> NetworkAlertContext {
|
||||
let alert = monitor::get_alerts().unwrap();
|
||||
// stored wifi data values as bytes
|
||||
let stored_traffic = monitor::get_data().unwrap();
|
||||
let threshold = monitor::get_thresholds().unwrap();
|
||||
// current wifi traffic values as bytes
|
||||
let traffic = match network_client::traffic("wlan0") {
|
||||
Ok(t) => t,
|
||||
Err(_) => Traffic {
|
||||
received: 0,
|
||||
transmitted: 0,
|
||||
rx_unit: None,
|
||||
tx_unit: None,
|
||||
},
|
||||
};
|
||||
|
||||
let current_traffic = traffic.received + traffic.transmitted;
|
||||
let total = stored_traffic.total + current_traffic;
|
||||
let data_total = Data { total };
|
||||
|
||||
NetworkAlertContext {
|
||||
alert,
|
||||
back: None,
|
||||
data_total,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
threshold,
|
||||
title: None,
|
||||
traffic,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkDetailContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub saved_aps: Vec<Networks>,
|
||||
pub selected: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub wlan_ip: String,
|
||||
pub wlan_networks: HashMap<String, AccessPoint>,
|
||||
pub wlan_rssi: Option<String>,
|
||||
pub wlan_ssid: String,
|
||||
pub wlan_state: String,
|
||||
pub wlan_status: String,
|
||||
pub wlan_traffic: Option<Traffic>,
|
||||
}
|
||||
|
||||
impl NetworkDetailContext {
|
||||
pub fn build() -> NetworkDetailContext {
|
||||
let wlan_ip = match network_client::ip("wlan0") {
|
||||
Ok(ip) => ip,
|
||||
Err(_) => "x.x.x.x".to_string(),
|
||||
};
|
||||
// list of networks saved in wpa_supplicant.conf
|
||||
let wlan_list = match network_client::saved_networks() {
|
||||
Ok(ssids) => {
|
||||
let networks: Vec<Networks> = serde_json::from_str(ssids.as_str())
|
||||
.expect("Failed to deserialize scan_list response");
|
||||
networks
|
||||
}
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
// list of networks saved in wpa_supplicant.conf
|
||||
// HACK: we're running the same function twice (wlan_list)
|
||||
// see if we can implement clone for Vec<Networks> instead
|
||||
let saved_aps = match network_client::saved_networks() {
|
||||
Ok(ssids) => {
|
||||
let networks: Vec<Networks> = serde_json::from_str(ssids.as_str())
|
||||
.expect("Failed to deserialize scan_list response");
|
||||
networks
|
||||
}
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
let wlan_rssi = match network_client::rssi_percent("wlan0") {
|
||||
Ok(rssi) => Some(rssi),
|
||||
Err(_) => None,
|
||||
};
|
||||
// list of networks currently in range (online & accessible)
|
||||
let wlan_scan = match network_client::available_networks("wlan0") {
|
||||
Ok(networks) => {
|
||||
let scan: Vec<Scan> = serde_json::from_str(networks.as_str())
|
||||
.expect("Failed to deserialize scan_networks response");
|
||||
scan
|
||||
}
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
let wlan_ssid = match network_client::ssid("wlan0") {
|
||||
Ok(ssid) => ssid,
|
||||
Err(_) => "Not connected".to_string(),
|
||||
};
|
||||
let wlan_state = match network_client::state("wlan0") {
|
||||
Ok(state) => state,
|
||||
Err(_) => "Interface unavailable".to_string(),
|
||||
};
|
||||
let wlan_status = match network_client::status("wlan0") {
|
||||
Ok(status) => status,
|
||||
Err(_) => "Interface unavailable".to_string(),
|
||||
};
|
||||
let wlan_traffic = match network_client::traffic("wlan0") {
|
||||
Ok(traffic) => {
|
||||
let mut t = traffic;
|
||||
// modify traffic values & assign measurement unit
|
||||
// based on received and transmitted values
|
||||
// if received > 999 MB, convert it to GB
|
||||
if t.received > 1_047_527_424 {
|
||||
t.received /= 1_073_741_824;
|
||||
t.rx_unit = Some("GB".to_string());
|
||||
} else if t.received > 0 {
|
||||
// otherwise, convert it to MB
|
||||
t.received = (t.received / 1024) / 1024;
|
||||
t.rx_unit = Some("MB".to_string());
|
||||
} else {
|
||||
t.received = 0;
|
||||
t.rx_unit = Some("MB".to_string());
|
||||
}
|
||||
|
||||
if t.transmitted > 1_047_527_424 {
|
||||
t.transmitted /= 1_073_741_824;
|
||||
t.tx_unit = Some("GB".to_string());
|
||||
} else if t.transmitted > 0 {
|
||||
t.transmitted = (t.transmitted / 1024) / 1024;
|
||||
t.tx_unit = Some("MB".to_string());
|
||||
} else {
|
||||
t.transmitted = 0;
|
||||
t.tx_unit = Some("MB".to_string());
|
||||
}
|
||||
Some(t)
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
// create a hashmap to combine wlan_list & wlan_scan without repetition
|
||||
let mut wlan_networks = HashMap::new();
|
||||
for ap in wlan_scan {
|
||||
let ssid = ap.ssid.clone();
|
||||
let rssi = ap.signal_level.clone();
|
||||
// parse the string to a signed integer (for math)
|
||||
let rssi_parsed = rssi.parse::<i32>().unwrap();
|
||||
// perform rssi (dBm) to quality (%) conversion
|
||||
let quality_percent = 2 * (rssi_parsed + 100);
|
||||
let ap_detail = AccessPoint {
|
||||
detail: Some(ap),
|
||||
state: "Available".to_string(),
|
||||
signal: Some(quality_percent),
|
||||
};
|
||||
wlan_networks.insert(ssid, ap_detail);
|
||||
}
|
||||
for network in wlan_list {
|
||||
// avoid repetition by checking that ssid is not already in list
|
||||
if !wlan_networks.contains_key(&network.ssid) {
|
||||
let ssid = network.ssid.clone();
|
||||
let net_detail = AccessPoint {
|
||||
detail: None,
|
||||
state: "Not in range".to_string(),
|
||||
signal: None,
|
||||
};
|
||||
wlan_networks.insert(ssid, net_detail);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkDetailContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
saved_aps,
|
||||
selected: None,
|
||||
title: None,
|
||||
wlan_ip,
|
||||
wlan_networks,
|
||||
wlan_rssi,
|
||||
wlan_ssid,
|
||||
wlan_state,
|
||||
wlan_status,
|
||||
wlan_traffic,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NetworkListContext {
|
||||
pub ap_state: String,
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub wlan_networks: HashMap<String, String>,
|
||||
pub wlan_ssid: String,
|
||||
}
|
||||
|
||||
impl NetworkListContext {
|
||||
pub fn build() -> NetworkListContext {
|
||||
// list of networks saved in the wpa_supplicant.conf
|
||||
let wlan_list = match network_client::saved_networks() {
|
||||
Ok(ssids) => {
|
||||
let networks: Vec<Networks> = serde_json::from_str(ssids.as_str())
|
||||
.expect("Failed to deserialize scan_list response");
|
||||
networks
|
||||
}
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
|
||||
// list of networks currently in range (online & accessible)
|
||||
let wlan_scan = match network_client::available_networks("wlan0") {
|
||||
Ok(networks) => {
|
||||
let scan: Vec<Networks> = serde_json::from_str(networks.as_str())
|
||||
.expect("Failed to deserialize scan_networks response");
|
||||
scan
|
||||
}
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
|
||||
let wlan_ssid = match network_client::ssid("wlan0") {
|
||||
Ok(ssid) => ssid,
|
||||
Err(_) => "Not connected".to_string(),
|
||||
};
|
||||
|
||||
// create a hashmap to combine wlan_list & wlan_scan without repetition
|
||||
let mut wlan_networks = HashMap::new();
|
||||
for ap in wlan_scan {
|
||||
wlan_networks.insert(ap.ssid, "Available".to_string());
|
||||
}
|
||||
for network in wlan_list {
|
||||
// insert ssid (with state) only if it doesn't already exist
|
||||
wlan_networks
|
||||
.entry(network.ssid)
|
||||
.or_insert_with(|| "Not in range".to_string());
|
||||
}
|
||||
|
||||
let ap_state = match network_client::state("ap0") {
|
||||
Ok(state) => state,
|
||||
Err(_) => "Interface unavailable".to_string(),
|
||||
};
|
||||
|
||||
NetworkListContext {
|
||||
ap_state,
|
||||
back: None,
|
||||
flash_msg: None,
|
||||
flash_name: None,
|
||||
title: None,
|
||||
wlan_networks,
|
||||
wlan_ssid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PeerContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl PeerContext {
|
||||
pub fn build() -> PeerContext {
|
||||
PeerContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ProfileContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl ProfileContext {
|
||||
pub fn build() -> ProfileContext {
|
||||
ProfileContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ShutdownContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl ShutdownContext {
|
||||
pub fn build() -> ShutdownContext {
|
||||
ShutdownContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
//! System calls for modifying the state of the PeachCloud device.
|
||||
|
||||
use std::io;
|
||||
use std::process::{Command, Output};
|
||||
|
||||
use log::info;
|
||||
|
||||
/// Executes a system command to reboot the device immediately.
|
||||
pub fn reboot() -> io::Result<Output> {
|
||||
info!("Rebooting the device");
|
||||
// ideally, we'd like to reboot after 5 seconds to allow time for JSON
|
||||
// response but this is not possible with the `shutdown` command alone.
|
||||
// TODO: send "rebooting..." message to `peach-oled` for display
|
||||
Command::new("sudo")
|
||||
.arg("shutdown")
|
||||
.arg("-r")
|
||||
.arg("now")
|
||||
.output()
|
||||
}
|
||||
|
||||
/// Executes a system command to shutdown the device immediately.
|
||||
pub fn shutdown() -> io::Result<Output> {
|
||||
info!("Shutting down the device");
|
||||
// ideally, we'd like to reboot after 5 seconds to allow time for JSON
|
||||
// response but this is not possible with the `shutdown` command alone.
|
||||
// TODO: send "shutting down..." message to `peach-oled` for display
|
||||
Command::new("sudo").arg("shutdown").arg("now").output()
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
//!! different types of PeachWebError
|
||||
//! Custom error type representing all possible error variants for peach-web.
|
||||
|
||||
use peach_lib::error::PeachError;
|
||||
use peach_lib::{serde_json, serde_yaml};
|
||||
|
@ -1,47 +0,0 @@
|
||||
//! Provides data structures which are used to parse forms from post requests.
|
||||
//!
|
||||
use rocket::request::FromForm;
|
||||
use rocket::UriDisplayQuery;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct DnsForm {
|
||||
pub external_domain: String,
|
||||
pub enable_dyndns: bool,
|
||||
pub dynamic_domain: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct PasswordForm {
|
||||
pub old_password: String,
|
||||
pub new_password1: String,
|
||||
pub new_password2: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct ResetPasswordForm {
|
||||
pub temporary_password: String,
|
||||
pub new_password1: String,
|
||||
pub new_password2: 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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct AddAdminForm {
|
||||
pub ssb_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct DeleteAdminForm {
|
||||
pub ssb_id: String,
|
||||
}
|
@ -1,539 +0,0 @@
|
||||
//! JSON API routes for PeachCloud.
|
||||
//!
|
||||
//! This module contains handlers which allow retrieval and modification of
|
||||
//! device state via JSON.
|
||||
//!
|
||||
//! API ROUTES
|
||||
//!
|
||||
//! | Method | URL | Description |
|
||||
//! | ------ | -------------------------------- | ----------------------------- |
|
||||
//! | POST | /api/v1/device/reboot | Reboot device |
|
||||
//! | POST | /api/v1/device/shutdown | Shutdown device |
|
||||
//! | POST | /api/v1/network/activate_ap | |
|
||||
//! | POST | /api/v1/network/activate_client | |
|
||||
//! | GET | /api/v1/network/ip | |
|
||||
//! | GET | /api/v1/network/rssi | |
|
||||
//! | GET | /api/v1/network/ssid | |
|
||||
//! | GET | /api/v1/network/state | |
|
||||
//! | GET | /api/v1/network/status | |
|
||||
//! | GET | /api/v1/network/wifi | Retrieve available networks |
|
||||
//! | POST | /api/v1/network/wifi | Add WiFi AP credentials |
|
||||
//! | POST | /api/v1/network/wifi/connect | Connect to WiFi access point |
|
||||
//! | POST | /api/v1/network/wifi/disconnect | Disconnect WiFi access point |
|
||||
//! | POST | /api/v1/network/wifi/forget | Forget / remove network |
|
||||
//! | POST | /api/v1/network/wifi/modify | Modify network password |
|
||||
//! | POST | /api/v1/network/wifi/usage | Update alert thresholds |
|
||||
//! | POST | /api/v1/network/wifi/usage/reset | Reset stored data usage total |
|
||||
//! | GET | /api/v1/ping | |
|
||||
//! | GET | /api/v1/ping/network | Ping `peach-network` |
|
||||
//! | GET | /api/v1/ping/oled | Ping `peach-oled` |
|
||||
//! | GET | /api/v1/ping/stats | Ping `peach-stats` |
|
||||
//! | POST | /api/v1/dns/configure | Modify dns configurations |
|
||||
//! | POST | /api/v1/settings/change_password | Change password (logged in) |
|
||||
//! | POST | /public/api/v1/reset_password | Change password (public) |
|
||||
|
||||
use log::{debug, warn};
|
||||
use rocket::{get, post};
|
||||
use rocket_contrib::json;
|
||||
use rocket_contrib::json::{Json, JsonValue};
|
||||
use serde::Serialize;
|
||||
|
||||
use peach_lib::dyndns_client::is_dns_updater_online;
|
||||
use peach_lib::network_client;
|
||||
use peach_lib::oled_client;
|
||||
use peach_lib::stats_client;
|
||||
use peach_lib::stats_client::Traffic;
|
||||
|
||||
use crate::common::{save_dns_configuration, save_password_form, save_reset_password_form};
|
||||
use crate::device;
|
||||
use crate::forms::{DnsForm, PasswordForm, ResetPasswordForm, Ssid, WiFi};
|
||||
use crate::monitor;
|
||||
use crate::monitor::Threshold;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct JsonResponse {
|
||||
pub status: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<JsonValue>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub msg: Option<String>,
|
||||
}
|
||||
|
||||
// reboot the device
|
||||
#[post("/api/v1/device/reboot")]
|
||||
pub fn reboot_device() -> Json<JsonResponse> {
|
||||
match device::reboot() {
|
||||
Ok(_) => {
|
||||
debug!("Going down for reboot...");
|
||||
let status = "success".to_string();
|
||||
let msg = "Going down for reboot.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown the device
|
||||
#[post("/api/v1/device/shutdown")]
|
||||
pub fn shutdown_device() -> Json<JsonResponse> {
|
||||
match device::shutdown() {
|
||||
Ok(_) => {
|
||||
debug!("Going down for shutdown...");
|
||||
let status = "success".to_string();
|
||||
let msg = "Going down for shutdown.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/activate_ap")]
|
||||
pub fn activate_ap() -> Json<JsonResponse> {
|
||||
// activate the wireless access point
|
||||
debug!("Activating WiFi access point.");
|
||||
match network_client::activate_ap() {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
Json(build_json_response(status, None, None))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to activate WiFi access point.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/activate_client")]
|
||||
pub fn activate_client() -> Json<JsonResponse> {
|
||||
// activate the wireless client
|
||||
debug!("Activating WiFi client mode.");
|
||||
match network_client::activate_client() {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
Json(build_json_response(status, None, None))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to activate WiFi client mode.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/v1/network/ip")]
|
||||
pub fn return_ip() -> Json<JsonResponse> {
|
||||
// retrieve ip for wlan0 or set to x.x.x.x if not found
|
||||
let wlan_ip = match network_client::ip("wlan0") {
|
||||
Ok(ip) => ip,
|
||||
Err(_) => "x.x.x.x".to_string(),
|
||||
};
|
||||
// retrieve ip for ap0 or set to x.x.x.x if not found
|
||||
let ap_ip = match network_client::ip("ap0") {
|
||||
Ok(ip) => ip,
|
||||
Err(_) => "x.x.x.x".to_string(),
|
||||
};
|
||||
let data = json!({
|
||||
"wlan0": wlan_ip,
|
||||
"ap0": ap_ip
|
||||
});
|
||||
let status = "success".to_string();
|
||||
Json(build_json_response(status, Some(data), None))
|
||||
}
|
||||
|
||||
#[get("/api/v1/network/rssi")]
|
||||
pub fn return_rssi() -> Json<JsonResponse> {
|
||||
// retrieve rssi for connected network
|
||||
match network_client::rssi("wlan0") {
|
||||
Ok(rssi) => {
|
||||
let status = "success".to_string();
|
||||
let data = json!(rssi);
|
||||
Json(build_json_response(status, Some(data), None))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Not currently connected to an access point.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/v1/network/ssid")]
|
||||
pub fn return_ssid() -> Json<JsonResponse> {
|
||||
// retrieve ssid for connected network
|
||||
match network_client::ssid("wlan0") {
|
||||
Ok(network) => {
|
||||
let status = "success".to_string();
|
||||
let data = json!(network);
|
||||
Json(build_json_response(status, Some(data), None))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Not currently connected to an access point.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/v1/network/state")]
|
||||
pub fn return_state() -> Json<JsonResponse> {
|
||||
// retrieve state of wlan0 or set to x.x.x.x if not found
|
||||
let wlan_state = match network_client::state("wlan0") {
|
||||
Ok(state) => state,
|
||||
Err(_) => "unavailable".to_string(),
|
||||
};
|
||||
// retrieve state for ap0 or set to x.x.x.x if not found
|
||||
let ap_state = match network_client::state("ap0") {
|
||||
Ok(state) => state,
|
||||
Err(_) => "unavailable".to_string(),
|
||||
};
|
||||
let data = json!({
|
||||
"wlan0": wlan_state,
|
||||
"ap0": ap_state
|
||||
});
|
||||
let status = "success".to_string();
|
||||
Json(build_json_response(status, Some(data), None))
|
||||
}
|
||||
|
||||
#[get("/api/v1/network/status")]
|
||||
pub fn return_status() -> Json<JsonResponse> {
|
||||
// retrieve status info for wlan0 interface
|
||||
match network_client::status("wlan0") {
|
||||
Ok(network) => {
|
||||
let status = "success".to_string();
|
||||
let data = json!(network);
|
||||
Json(build_json_response(status, Some(data), None))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Not currently connected to an access point.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/v1/network/wifi")]
|
||||
pub fn scan_networks() -> Json<JsonResponse> {
|
||||
// retrieve scan results for access-points within range of wlan0
|
||||
match network_client::available_networks("wlan0") {
|
||||
Ok(networks) => {
|
||||
let status = "success".to_string();
|
||||
let data = json!(networks);
|
||||
Json(build_json_response(status, Some(data), None))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Unable to scan for networks. Interface may be deactivated.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/wifi", data = "<wifi>")]
|
||||
pub fn add_wifi(wifi: Json<WiFi>) -> Json<JsonResponse> {
|
||||
// generate and write wifi config to wpa_supplicant
|
||||
match network_client::add(&wifi.ssid, &wifi.pass) {
|
||||
Ok(_) => {
|
||||
debug!("Added WiFi credentials.");
|
||||
// force reread of wpa_supplicant.conf file with new credentials
|
||||
match network_client::reconfigure() {
|
||||
Ok(_) => debug!("Successfully reconfigured wpa_supplicant."),
|
||||
Err(_) => warn!("Failed to reconfigure wpa_supplicant."),
|
||||
}
|
||||
// json response for successful update
|
||||
let status = "success".to_string();
|
||||
let msg = "WiFi credentials added.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Failed to add WiFi credentials.");
|
||||
// json response for failed update
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to add WiFi credentials.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/wifi/connect", data = "<ssid>")]
|
||||
pub fn connect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
|
||||
// retrieve the id for the given network ssid
|
||||
match network_client::id("wlan0", &ssid.ssid) {
|
||||
// attempt connection with the given network
|
||||
Ok(id) => match network_client::connect(&id, "wlan0") {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Connected to chosen network.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to connect to chosen network.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to retrieve the network ID.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/wifi/disconnect", data = "<ssid>")]
|
||||
pub fn disconnect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
|
||||
// attempt to disable the current network for wlan0 interface
|
||||
match network_client::disable("wlan0", &ssid.ssid) {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Disconnected from WiFi network.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_) => {
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to disconnect from WiFi network.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/wifi/forget", data = "<network>")]
|
||||
pub fn forget_ap(network: Json<Ssid>) -> Json<JsonResponse> {
|
||||
let ssid = &network.ssid;
|
||||
match network_client::forget("wlan0", ssid) {
|
||||
Ok(_) => {
|
||||
debug!("Removed WiFi credentials for chosen network.");
|
||||
let status = "success".to_string();
|
||||
let msg = "WiFi network credentials removed.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Failed to remove WiFi credentials.");
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to remove WiFi network credentials.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/wifi/modify", data = "<wifi>")]
|
||||
pub fn modify_password(wifi: Json<WiFi>) -> Json<JsonResponse> {
|
||||
let ssid = &wifi.ssid;
|
||||
let pass = &wifi.pass;
|
||||
// we are using a helper function (`update`) to delete the old
|
||||
// credentials and add the new ones. this is because the wpa_cli method
|
||||
// for updating the password does not work.
|
||||
match network_client::update("wlan0", ssid, pass) {
|
||||
Ok(_) => {
|
||||
debug!("WiFi password updated for chosen network.");
|
||||
let status = "success".to_string();
|
||||
let msg = "WiFi password updated.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Failed to update WiFi password.");
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to update WiFi password.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/wifi/usage", data = "<thresholds>")]
|
||||
pub fn update_wifi_alerts(thresholds: Json<Threshold>) -> Json<JsonResponse> {
|
||||
match monitor::update_store(thresholds.into_inner()) {
|
||||
Ok(_) => {
|
||||
debug!("WiFi data usage thresholds updated.");
|
||||
let status = "success".to_string();
|
||||
let msg = "Updated alert threshold and flags.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Failed to update WiFi data usage thresholds.");
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to update WiFi data usage thresholds.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/network/wifi/usage/reset")]
|
||||
pub fn reset_data_total() -> Json<JsonResponse> {
|
||||
match monitor::reset_data() {
|
||||
Ok(_) => {
|
||||
debug!("Reset network data usage total.");
|
||||
let traffic = match network_client::traffic("wlan0") {
|
||||
Ok(t) => t,
|
||||
Err(_) => Traffic {
|
||||
received: 0,
|
||||
transmitted: 0,
|
||||
rx_unit: None,
|
||||
tx_unit: None,
|
||||
},
|
||||
};
|
||||
// current wifi traffic values as bytes
|
||||
let current_traffic = traffic.received + traffic.transmitted;
|
||||
let data = json!(current_traffic);
|
||||
let status = "success".to_string();
|
||||
let msg = "Reset network data usage total.".to_string();
|
||||
Json(build_json_response(status, Some(data), Some(msg)))
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Failed to reset network data usage total.");
|
||||
let status = "error".to_string();
|
||||
let msg = "Failed to reset network data usage total.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status route: useful for checking connectivity from web client
|
||||
#[get("/api/v1/ping")]
|
||||
pub fn ping_pong() -> Json<JsonResponse> {
|
||||
// ping pong
|
||||
let status = "success".to_string();
|
||||
let msg = "pong!".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
|
||||
// test route: useful for ad hoc testing
|
||||
#[get("/api/v1/test")]
|
||||
pub fn test_route() -> Json<JsonResponse> {
|
||||
let val = is_dns_updater_online().unwrap();
|
||||
let status = "success".to_string();
|
||||
let msg = val.to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
|
||||
// status route: check availability of `peach-network` microservice
|
||||
#[get("/api/v1/ping/network")]
|
||||
pub fn ping_network() -> Json<JsonResponse> {
|
||||
match network_client::ping() {
|
||||
Ok(_) => {
|
||||
debug!("peach-network responded successfully");
|
||||
let status = "success".to_string();
|
||||
let msg = "peach-network is available.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status route: check availability of `peach-oled` microservice
|
||||
#[get("/api/v1/ping/oled")]
|
||||
pub fn ping_oled() -> Json<JsonResponse> {
|
||||
match oled_client::ping() {
|
||||
Ok(_) => {
|
||||
debug!("peach-oled responded successfully");
|
||||
let status = "success".to_string();
|
||||
let msg = "peach-oled is available.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status route: check availability of `peach-stats` microservice
|
||||
#[get("/api/v1/ping/stats")]
|
||||
pub fn ping_stats() -> Json<JsonResponse> {
|
||||
match stats_client::ping() {
|
||||
Ok(_) => {
|
||||
debug!("peach-stats responded successfully");
|
||||
let status = "success".to_string();
|
||||
let msg = "peach-stats is available.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/dns/configure", data = "<dns_form>")]
|
||||
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>) -> Json<JsonResponse> {
|
||||
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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = format!("{}", err);
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/settings/change_password", data = "<password_form>")]
|
||||
pub fn save_password_form_endpoint(password_form: Json<PasswordForm>) -> Json<JsonResponse> {
|
||||
let result = save_password_form(password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Your password was successfully changed".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = format!("{}", err);
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// this reset password route is used by a user who is not logged in
|
||||
/// and is specifically for users who have forgotten their password
|
||||
/// all routes under /public/* are excluded from nginx basic auth via the nginx config
|
||||
#[post("/public/api/v1/reset_password", data = "<reset_password_form>")]
|
||||
pub fn reset_password_form_endpoint(
|
||||
reset_password_form: Json<ResetPasswordForm>,
|
||||
) -> Json<JsonResponse> {
|
||||
let result = save_reset_password_form(reset_password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "New password is now saved. Return home to login.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = format!("{}", err);
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
pub fn build_json_response(
|
||||
status: String,
|
||||
data: Option<JsonValue>,
|
||||
msg: Option<String>,
|
||||
) -> JsonResponse {
|
||||
JsonResponse { status, data, msg }
|
||||
}
|
@ -24,20 +24,12 @@
|
||||
//! of the template to be rendered.
|
||||
|
||||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
// this is to ignore a clippy warning that suggests
|
||||
// to replace code with the same code that is already there (possibly a bug)
|
||||
#![allow(clippy::nonstandard_macro_braces)]
|
||||
|
||||
pub mod common;
|
||||
pub mod context;
|
||||
pub mod device;
|
||||
pub mod error;
|
||||
pub mod forms;
|
||||
pub mod json_api;
|
||||
pub mod monitor;
|
||||
pub mod routes;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod utils;
|
||||
mod ws;
|
||||
|
||||
use std::{env, thread};
|
||||
@ -47,8 +39,16 @@ use log::{debug, error, info};
|
||||
use rocket::{catchers, routes};
|
||||
use rocket_contrib::templates::Template;
|
||||
|
||||
use crate::json_api::*;
|
||||
use crate::routes::*;
|
||||
use crate::routes::authentication::*;
|
||||
use crate::routes::device::*;
|
||||
use crate::routes::helpers::*;
|
||||
use crate::routes::index::*;
|
||||
use crate::routes::ping::*;
|
||||
use crate::routes::scuttlebutt::*;
|
||||
|
||||
use crate::routes::settings::admin::*;
|
||||
use crate::routes::settings::dns::*;
|
||||
use crate::routes::settings::network::*;
|
||||
use crate::ws::*;
|
||||
|
||||
pub type BoxError = Box<dyn std::error::Error>;
|
||||
|
@ -1,744 +0,0 @@
|
||||
//! Route handlers for PeachCloud web routes.
|
||||
//!
|
||||
//! This module contains handlers which serve templates and static assests,
|
||||
//! generate flash messages, catch errors and handle redirects for PeachCloud.
|
||||
//!
|
||||
//! WEB ROUTES
|
||||
//!
|
||||
//! | Method | URL | Description |
|
||||
//! | ------ | --------------------------- | --------------------------------- |
|
||||
//! | GET | / | Home |
|
||||
//! | GET | /device | Device statistics |
|
||||
//! | GET | /device/reboot | Reboot device |
|
||||
//! | GET | /device/shutdown | Shutdown device |
|
||||
//! | GET | /help | Help and usage guidelines |
|
||||
//! | GET | /login | Login form |
|
||||
//! | POST | /login | Login form submission |
|
||||
//! | POST | /logout | Logout authenticated user |
|
||||
//! | GET | /network | Network overview |
|
||||
//! | GET | /network/ap/activate | Activate WiFi access point mode |
|
||||
//! | GET | /network/wifi | List of networks |
|
||||
//! | GET | /network/wifi?<ssid> | Details of single network |
|
||||
//! | GET | /network/wifi/activate | Activate WiFi client mode |
|
||||
//! | GET | /network/wifi/add | Add WiFi form |
|
||||
//! | POST | /network/wifi/add | WiFi form submission |
|
||||
//! | GET | /network/wifi/add?<ssid> | Add WiFi form (SSID populated) |
|
||||
//! | POST | /network/wifi/connect | Connect to WiFi access point |
|
||||
//! | POST | /network/wifi/disconnect | Disconnect from WiFi access point |
|
||||
//! | POST | /network/wifi/forget | Remove WiFi |
|
||||
//! | GET | /network/wifi/modify?<ssid> | Modify WiFi password form |
|
||||
//! | POST | /network/wifi/modify | Modify network password |
|
||||
//! | GET | /network/wifi/usage | WiFi data usage form |
|
||||
//! | POST | /network/wifi/usage | WiFi data usage form submission |
|
||||
//! | GET | /network/wifi/usage/reset | Reset stored data usage total |
|
||||
//! | GET | /messages | Private Scuttlebutt messages |
|
||||
//! | GET | /peers | Scuttlebutt peers overview |
|
||||
//! | GET | /profile | Scuttlebutt user profile |
|
||||
//! | GET | /shutdown | Shutdown menu |
|
||||
//! | GET | /network/dns | View DNS configurations |
|
||||
//! | POST | /network/dns | Modify DNS configurations |
|
||||
//! | GET | /settings/change_password | View password settings form |
|
||||
//! | POST | /settings/change_password | Change admin password |
|
||||
//! | GET | /reset_password | Change password using temp pass |
|
||||
//! | POST | /reset_password | Rhange password using temp pass |
|
||||
//! | GET | /send_password_reset | Send new password reset link |
|
||||
//! | POST | /send_password_reset | Send new password reset link |
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log::{debug, info, warn};
|
||||
use percent_encoding::percent_decode;
|
||||
use rocket::http::RawStr;
|
||||
use rocket::request::{FlashMessage, Form};
|
||||
use rocket::response::{Flash, NamedFile, Redirect};
|
||||
use rocket::{catch, get, post, uri};
|
||||
use rocket_contrib::templates::Template;
|
||||
|
||||
use peach_lib::config_manager;
|
||||
use peach_lib::network_client;
|
||||
use peach_lib::password_utils;
|
||||
|
||||
use crate::common::{
|
||||
save_add_admin_form, save_dns_configuration, save_password_form, save_reset_password_form,
|
||||
};
|
||||
use crate::context::{
|
||||
AddAdminContext, ChangePasswordContext, ConfigureAdminContext, ConfigureDNSContext,
|
||||
DeviceContext, ErrorContext, HelpContext, HomeContext, LoginContext, MessageContext,
|
||||
NetworkAddContext, NetworkAlertContext, NetworkContext, NetworkDetailContext,
|
||||
NetworkListContext, PeerContext, ProfileContext, ResetPasswordContext,
|
||||
SendPasswordResetContext, ShutdownContext,
|
||||
};
|
||||
use crate::device;
|
||||
use crate::forms::{
|
||||
AddAdminForm, DeleteAdminForm, DnsForm, PasswordForm, ResetPasswordForm, Ssid, WiFi,
|
||||
};
|
||||
use crate::monitor;
|
||||
use crate::monitor::Threshold;
|
||||
|
||||
#[get("/")]
|
||||
pub fn index() -> Template {
|
||||
let context = HomeContext {
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
};
|
||||
Template::render("index", &context)
|
||||
}
|
||||
|
||||
#[get("/device")]
|
||||
pub fn device_stats(flash: Option<FlashMessage>) -> Template {
|
||||
// assign context through context_builder call
|
||||
let mut context = DeviceContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Device Status".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("device", &context)
|
||||
}
|
||||
|
||||
#[get("/device/reboot")]
|
||||
pub fn reboot_cmd() -> Flash<Redirect> {
|
||||
match device::reboot() {
|
||||
Ok(_) => Flash::success(Redirect::to("/shutdown"), "Rebooting the device"),
|
||||
Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to reboot the device"),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/device/shutdown")]
|
||||
pub fn shutdown_cmd() -> Flash<Redirect> {
|
||||
match device::shutdown() {
|
||||
Ok(_) => Flash::success(Redirect::to("/shutdown"), "Shutting down the device"),
|
||||
Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to shutdown the device"),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/help")]
|
||||
pub fn help(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = HelpContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Help".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("help", &context)
|
||||
}
|
||||
|
||||
#[get("/login")]
|
||||
pub fn login(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = LoginContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Login".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("login", &context)
|
||||
}
|
||||
|
||||
#[post("/logout")]
|
||||
pub fn logout() -> Flash<Redirect> {
|
||||
// logout authenticated user
|
||||
debug!("Attempting deauthentication of user.");
|
||||
/*
|
||||
match logout_user() {
|
||||
Ok(_) => Flash::success(Redirect::to("/"), "Logout success"),
|
||||
Err(_) => Flash::error(
|
||||
Redirect::to("/"),
|
||||
"Failed to logout",
|
||||
),
|
||||
}
|
||||
*/
|
||||
Flash::success(Redirect::to("/"), "Logged out")
|
||||
}
|
||||
|
||||
#[get("/network")]
|
||||
pub fn network_home(flash: Option<FlashMessage>) -> Template {
|
||||
// assign context through context_builder call
|
||||
let mut context = NetworkContext::build();
|
||||
// set back button (nav) url
|
||||
context.back = Some("/".to_string());
|
||||
// set page title
|
||||
context.title = Some("Network Configuration".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("network_card", &context)
|
||||
}
|
||||
|
||||
#[get("/network/ap/activate")]
|
||||
pub fn deploy_ap() -> Flash<Redirect> {
|
||||
// activate the wireless access point
|
||||
debug!("Activating WiFi access point.");
|
||||
match network_client::activate_ap() {
|
||||
Ok(_) => Flash::success(Redirect::to("/network"), "Activated WiFi access point"),
|
||||
Err(_) => Flash::error(
|
||||
Redirect::to("/network"),
|
||||
"Failed to activate WiFi access point",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/network/wifi")]
|
||||
pub fn wifi_list(flash: Option<FlashMessage>) -> Template {
|
||||
// assign context through context_builder call
|
||||
let mut context = NetworkListContext::build();
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("WiFi Networks".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("network_list", &context)
|
||||
}
|
||||
|
||||
#[get("/network/wifi?<ssid>")]
|
||||
pub fn network_detail(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
|
||||
// assign context through context_builder call
|
||||
let mut context = NetworkDetailContext::build();
|
||||
context.back = Some("/network/wifi".to_string());
|
||||
context.title = Some("WiFi Network".to_string());
|
||||
// decode ssid from url
|
||||
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
|
||||
context.selected = Some(decoded_ssid.to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("network_detail", &context)
|
||||
}
|
||||
|
||||
#[get("/network/wifi/activate")]
|
||||
pub fn deploy_client() -> Flash<Redirect> {
|
||||
// activate the wireless client
|
||||
debug!("Activating WiFi client mode.");
|
||||
match network_client::activate_client() {
|
||||
Ok(_) => Flash::success(Redirect::to("/network"), "Activated WiFi client"),
|
||||
Err(_) => Flash::error(Redirect::to("/network"), "Failed to activate WiFi client"),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/network/wifi/add")]
|
||||
pub fn network_add_wifi(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = NetworkContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Add WiFi Network".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("network_add", &context)
|
||||
}
|
||||
|
||||
#[get("/network/wifi/add?<ssid>")]
|
||||
pub fn network_add_ssid(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
|
||||
// decode ssid from url
|
||||
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
|
||||
let mut context = NetworkAddContext::build();
|
||||
context.back = Some("/network/wifi".to_string());
|
||||
context.selected = Some(decoded_ssid.to_string());
|
||||
context.title = Some("Add WiFi Network".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("network_add", &context)
|
||||
}
|
||||
|
||||
#[post("/network/wifi/add", data = "<wifi>")]
|
||||
pub fn add_credentials(wifi: Form<WiFi>) -> Template {
|
||||
// check if the credentials already exist for this access point
|
||||
// note: this is nicer but it's an unstable feature:
|
||||
// if check_saved_aps(&wifi.ssid).contains(true)
|
||||
// use unwrap_or instead, set value to false if err is returned
|
||||
let creds_exist = network_client::saved_ap(&wifi.ssid).unwrap_or(false);
|
||||
if creds_exist {
|
||||
let mut context = NetworkAddContext::build();
|
||||
context.back = Some("/network".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg =
|
||||
Some("Network credentials already exist for this access point".to_string());
|
||||
context.title = Some("Add WiFi Network".to_string());
|
||||
// return early from handler with "creds already exist" message
|
||||
return Template::render("network_add", &context);
|
||||
};
|
||||
|
||||
// if credentials not found, generate and write wifi config to wpa_supplicant
|
||||
match network_client::add(&wifi.ssid, &wifi.pass) {
|
||||
Ok(_) => {
|
||||
debug!("Added WiFi credentials.");
|
||||
// force reread of wpa_supplicant.conf file with new credentials
|
||||
match network_client::reconfigure() {
|
||||
Ok(_) => debug!("Successfully reconfigured wpa_supplicant"),
|
||||
Err(_) => warn!("Failed to reconfigure wpa_supplicant"),
|
||||
}
|
||||
let mut context = NetworkAddContext::build();
|
||||
context.back = Some("/network".to_string());
|
||||
context.flash_name = Some("success".to_string());
|
||||
context.flash_msg = Some("Added WiFi credentials".to_string());
|
||||
context.title = Some("Add WiFi Network".to_string());
|
||||
Template::render("network_add", &context)
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Failed to add WiFi credentials.");
|
||||
let mut context = NetworkAddContext::build();
|
||||
context.back = Some("/network".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some("Failed to add WiFi credentials".to_string());
|
||||
context.title = Some("Add WiFi Network".to_string());
|
||||
Template::render("network_add", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/network/wifi/usage")]
|
||||
pub fn wifi_usage(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = NetworkAlertContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Network Data Usage".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("network_usage", &context)
|
||||
}
|
||||
|
||||
#[post("/network/wifi/usage", data = "<thresholds>")]
|
||||
pub fn wifi_usage_alerts(thresholds: Form<Threshold>) -> Flash<Redirect> {
|
||||
match monitor::update_store(thresholds.into_inner()) {
|
||||
Ok(_) => {
|
||||
debug!("WiFi data usage thresholds updated.");
|
||||
Flash::success(
|
||||
Redirect::to("/network/wifi/usage"),
|
||||
"Updated alert thresholds and flags",
|
||||
)
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Failed to update WiFi data usage thresholds.");
|
||||
Flash::error(
|
||||
Redirect::to("/network/wifi/usage"),
|
||||
"Failed to update alert thresholds and flags",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/network/dns")]
|
||||
pub fn configure_dns(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ConfigureDNSContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("configure_dns", &context)
|
||||
}
|
||||
|
||||
#[post("/network/dns", data = "<dns>")]
|
||||
pub fn configure_dns_post(dns: Form<DnsForm>) -> Template {
|
||||
let result = save_dns_configuration(dns.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ConfigureDNSContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
context.flash_name = Some("success".to_string());
|
||||
context.flash_msg = Some("New dynamic dns configuration is now enabled".to_string());
|
||||
Template::render("configure_dns", &context)
|
||||
}
|
||||
Err(err) => {
|
||||
let mut context = ConfigureDNSContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some(format!("Failed to save dns configurations: {}", err));
|
||||
Template::render("configure_dns", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// this change password route is used by a user who is already logged in
|
||||
#[get("/settings/change_password")]
|
||||
pub fn change_password(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Change Password".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("password/change_password", &context)
|
||||
}
|
||||
|
||||
/// this change password route is used by a user who is already logged in
|
||||
#[post("/settings/change_password", data = "<password_form>")]
|
||||
pub fn change_password_post(password_form: Form<PasswordForm>) -> Template {
|
||||
let result = save_password_form(password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".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("password/change_password", &context)
|
||||
}
|
||||
Err(err) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some(format!("Failed to save new password: {}", err));
|
||||
Template::render("password/change_password", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// this reset password route is used by a user who is not logged in
|
||||
/// and is specifically for users who have forgotten their password
|
||||
/// all routes under /public/* are excluded from nginx basic auth via the nginx config
|
||||
#[get("/reset_password")]
|
||||
pub fn reset_password(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ResetPasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Reset Password".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("password/reset_password", &context)
|
||||
}
|
||||
|
||||
/// this reset password route is used by a user who is not logged in
|
||||
/// and is specifically for users who have forgotten their password
|
||||
/// and is excluded from nginx basic auth via the nginx config
|
||||
#[post("/reset_password", data = "<reset_password_form>")]
|
||||
pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> Template {
|
||||
let result = save_reset_password_form(reset_password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Reset Password".to_string());
|
||||
context.flash_name = Some("success".to_string());
|
||||
let flash_msg = "New password is now saved. Return home to login".to_string();
|
||||
context.flash_msg = Some(flash_msg);
|
||||
Template::render("password/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("password/reset_password", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// this route is used by a user who is not logged in to send a new password reset link
|
||||
#[get("/send_password_reset")]
|
||||
pub fn send_password_reset_page(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = SendPasswordResetContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Send Password Reset".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("password/send_password_reset", &context)
|
||||
}
|
||||
|
||||
/// this send_password_reset route is used by a user who is not logged in
|
||||
/// and is specifically for users who have forgotten their password
|
||||
#[post("/send_password_reset")]
|
||||
pub fn send_password_reset_post() -> Template {
|
||||
info!("++ send password reset post");
|
||||
let result = password_utils::send_password_reset();
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Send Password Reset".to_string());
|
||||
context.flash_name = Some("success".to_string());
|
||||
let flash_msg =
|
||||
"A password reset link has been sent to the admin of this device".to_string();
|
||||
context.flash_msg = Some(flash_msg);
|
||||
Template::render("password/send_password_reset", &context)
|
||||
}
|
||||
Err(err) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Send Password Reset".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some(format!("Failed to send password reset link: {}", err));
|
||||
Template::render("password/send_password_reset", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// this is a route for viewing and deleting currently configured admin
|
||||
#[get("/settings/configure_admin")]
|
||||
pub fn configure_admin(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ConfigureAdminContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure Admin".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("admin/configure_admin", &context)
|
||||
}
|
||||
|
||||
#[get("/settings/admin/add")]
|
||||
pub fn add_admin(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = AddAdminContext::build();
|
||||
context.back = Some("/settings/configure_admin".to_string());
|
||||
context.title = Some("Add Admin".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("admin/add_admin", &context)
|
||||
}
|
||||
|
||||
#[post("/settings/admin/add", data = "<add_admin_form>")]
|
||||
pub fn add_admin_post(add_admin_form: Form<AddAdminForm>) -> Flash<Redirect> {
|
||||
let result = save_add_admin_form(add_admin_form.into_inner());
|
||||
let url = uri!(configure_admin);
|
||||
match result {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"),
|
||||
Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/settings/admin/delete", data = "<delete_admin_form>")]
|
||||
pub fn delete_admin_post(delete_admin_form: Form<DeleteAdminForm>) -> Flash<Redirect> {
|
||||
let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id);
|
||||
let url = uri!(configure_admin);
|
||||
match result {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "Successfully removed admin id"),
|
||||
Err(_) => Flash::error(Redirect::to(url), "Failed to remove admin id"),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/network/wifi/usage/reset")]
|
||||
pub fn wifi_usage_reset() -> Flash<Redirect> {
|
||||
let url = uri!(wifi_usage);
|
||||
match monitor::reset_data() {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "Reset stored network traffic total"),
|
||||
Err(_) => Flash::error(
|
||||
Redirect::to(url),
|
||||
"Failed to reset stored network traffic total",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/network/wifi/connect", data = "<network>")]
|
||||
pub fn connect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
|
||||
let ssid = &network.ssid;
|
||||
let url = uri!(network_detail: ssid);
|
||||
match network_client::id("wlan0", ssid) {
|
||||
Ok(id) => match network_client::connect(&id, "wlan0") {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "Connected to chosen network"),
|
||||
Err(_) => Flash::error(Redirect::to(url), "Failed to connect to chosen network"),
|
||||
},
|
||||
Err(_) => Flash::error(Redirect::to(url), "Failed to retrieve the network ID"),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/network/wifi/disconnect", data = "<network>")]
|
||||
pub fn disconnect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
|
||||
let ssid = &network.ssid;
|
||||
let url = uri!(network_home);
|
||||
match network_client::disable("wlan0", ssid) {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "Disconnected from WiFi network"),
|
||||
Err(_) => Flash::error(Redirect::to(url), "Failed to disconnect from WiFi network"),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/network/wifi/forget", data = "<network>")]
|
||||
pub fn forget_wifi(network: Form<Ssid>) -> Flash<Redirect> {
|
||||
let ssid = &network.ssid;
|
||||
let url = uri!(network_home);
|
||||
match network_client::forget("wlan0", ssid) {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "WiFi credentials removed"),
|
||||
Err(_) => Flash::error(
|
||||
Redirect::to(url),
|
||||
"Failed to remove WiFi credentials".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/network/wifi/modify?<ssid>")]
|
||||
pub fn wifi_password(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
|
||||
// decode ssid from url
|
||||
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
|
||||
let mut context = NetworkAddContext {
|
||||
back: Some("/network/wifi".to_string()),
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
selected: Some(decoded_ssid.to_string()),
|
||||
title: Some("Update WiFi Password".to_string()),
|
||||
};
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("network_modify", &context)
|
||||
}
|
||||
|
||||
#[post("/network/wifi/modify", data = "<wifi>")]
|
||||
pub fn wifi_set_password(wifi: Form<WiFi>) -> Flash<Redirect> {
|
||||
let ssid = &wifi.ssid;
|
||||
let pass = &wifi.pass;
|
||||
let url = uri!(network_detail: ssid);
|
||||
match network_client::update("wlan0", ssid, pass) {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "WiFi password updated".to_string()),
|
||||
Err(_) => Flash::error(
|
||||
Redirect::to(url),
|
||||
"Failed to update WiFi password".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/messages")]
|
||||
pub fn messages(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = MessageContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Private Messages".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("messages", &context)
|
||||
}
|
||||
|
||||
#[get("/peers")]
|
||||
pub fn peers(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = PeerContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Scuttlebutt Peers".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("peers", &context)
|
||||
}
|
||||
|
||||
#[get("/profile")]
|
||||
pub fn profile(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ProfileContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Profile".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("profile", &context)
|
||||
}
|
||||
|
||||
#[get("/shutdown")]
|
||||
pub fn shutdown_menu(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ShutdownContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Shutdown Device".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("shutdown", &context)
|
||||
}
|
||||
|
||||
#[get("/<file..>", rank = 2)]
|
||||
pub fn files(file: PathBuf) -> Option<NamedFile> {
|
||||
NamedFile::open(Path::new("static/").join(file)).ok()
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
pub fn not_found() -> Template {
|
||||
debug!("404 Page Not Found");
|
||||
let mut context = ErrorContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("404: Page Not Found".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some("No resource found for given URL".to_string());
|
||||
|
||||
Template::render("not_found", context)
|
||||
}
|
||||
|
||||
#[catch(500)]
|
||||
pub fn internal_error() -> Template {
|
||||
debug!("500 Internal Server Error");
|
||||
let mut context = ErrorContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("500: Internal Server Error".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some("Internal server error".to_string());
|
||||
|
||||
Template::render("internal_error", context)
|
||||
}
|
347
peach-web/src/routes/authentication.rs
Normal file
347
peach-web/src/routes/authentication.rs
Normal file
@ -0,0 +1,347 @@
|
||||
use log::{debug, info};
|
||||
use rocket::request::{FlashMessage, Form, FromForm};
|
||||
use rocket::response::{Flash, Redirect};
|
||||
use rocket::{get, post};
|
||||
use rocket_contrib::{json::Json, templates::Template};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use peach_lib::password_utils;
|
||||
|
||||
use crate::error::PeachWebError;
|
||||
use crate::utils::{build_json_response, JsonResponse};
|
||||
|
||||
// HELPERS AND ROUTES FOR /login
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct LoginContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl LoginContext {
|
||||
pub fn build() -> LoginContext {
|
||||
LoginContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/login")]
|
||||
pub fn login(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = LoginContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Login".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("login", &context)
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /logout
|
||||
|
||||
#[post("/logout")]
|
||||
pub fn logout() -> Flash<Redirect> {
|
||||
// logout authenticated user
|
||||
debug!("Attempting deauthentication of user.");
|
||||
/*
|
||||
match logout_user() {
|
||||
Ok(_) => Flash::success(Redirect::to("/"), "Logout success"),
|
||||
Err(_) => Flash::error(
|
||||
Redirect::to("/"),
|
||||
"Failed to logout",
|
||||
),
|
||||
}
|
||||
*/
|
||||
Flash::success(Redirect::to("/"), "Logged out")
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /reset_password
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct ResetPasswordForm {
|
||||
pub temporary_password: String,
|
||||
pub new_password1: 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.
|
||||
pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), PeachWebError> {
|
||||
info!(
|
||||
"reset password!: {} {} {}",
|
||||
password_form.temporary_password, password_form.new_password1, password_form.new_password2
|
||||
);
|
||||
password_utils::verify_temporary_password(&password_form.temporary_password)?;
|
||||
// if the previous line did not throw an error, then the secret_link is correct
|
||||
password_utils::validate_new_passwords(
|
||||
&password_form.new_password1,
|
||||
&password_form.new_password2,
|
||||
)?;
|
||||
// if the previous line did not throw an error, then the new password is valid
|
||||
password_utils::set_new_password(&password_form.new_password1)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Password reset request handler. This route is used by a user who is not logged in
|
||||
/// and is specifically for users who have forgotten their password.
|
||||
/// All routes under /public/* are excluded from nginx basic auth via the nginx config.
|
||||
#[get("/reset_password")]
|
||||
pub fn reset_password(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ResetPasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Reset Password".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("password/reset_password", &context)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// This route is excluded from nginx basic auth via the nginx config.
|
||||
#[post("/reset_password", data = "<reset_password_form>")]
|
||||
pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> Template {
|
||||
let result = save_reset_password_form(reset_password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Reset Password".to_string());
|
||||
context.flash_name = Some("success".to_string());
|
||||
let flash_msg = "New password is now saved. Return home to login".to_string();
|
||||
context.flash_msg = Some(flash_msg);
|
||||
Template::render("password/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("password/reset_password", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON password reset form request handler. This route is used by a user who is not logged in
|
||||
/// and is specifically for users who have forgotten their password.
|
||||
/// All routes under /public/* are excluded from nginx basic auth via the nginx config.
|
||||
#[post("/public/api/v1/reset_password", data = "<reset_password_form>")]
|
||||
pub fn reset_password_form_endpoint(
|
||||
reset_password_form: Json<ResetPasswordForm>,
|
||||
) -> Json<JsonResponse> {
|
||||
let result = save_reset_password_form(reset_password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "New password is now saved. Return home to login.".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = format!("{}", err);
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Password reset request handler. This route is used by a user who is not logged in to send a new password reset link.
|
||||
#[get("/send_password_reset")]
|
||||
pub fn send_password_reset_page(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = SendPasswordResetContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Send Password Reset".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("password/send_password_reset", &context)
|
||||
}
|
||||
|
||||
/// Send password reset request handler. This route is used by a user who is not logged in
|
||||
/// and is specifically for users who have forgotten their password. A successful request results
|
||||
/// in a Scuttlebutt private message being sent to the account of the device admin.
|
||||
#[post("/send_password_reset")]
|
||||
pub fn send_password_reset_post() -> Template {
|
||||
info!("++ send password reset post");
|
||||
let result = password_utils::send_password_reset();
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Send Password Reset".to_string());
|
||||
context.flash_name = Some("success".to_string());
|
||||
let flash_msg =
|
||||
"A password reset link has been sent to the admin of this device".to_string();
|
||||
context.flash_msg = Some(flash_msg);
|
||||
Template::render("password/send_password_reset", &context)
|
||||
}
|
||||
Err(err) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Send Password Reset".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some(format!("Failed to send password reset link: {}", err));
|
||||
Template::render("password/send_password_reset", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /settings/change_password
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct PasswordForm {
|
||||
pub old_password: String,
|
||||
pub new_password1: String,
|
||||
pub new_password2: String,
|
||||
}
|
||||
|
||||
/// Password save form request handler. This function is for use by a user who is already logged in to change their password.
|
||||
pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> {
|
||||
info!(
|
||||
"change password!: {} {} {}",
|
||||
password_form.old_password, password_form.new_password1, password_form.new_password2
|
||||
);
|
||||
password_utils::verify_password(&password_form.old_password)?;
|
||||
// if the previous line did not throw an error, then the old password is correct
|
||||
password_utils::validate_new_passwords(
|
||||
&password_form.new_password1,
|
||||
&password_form.new_password2,
|
||||
)?;
|
||||
// if the previous line did not throw an error, then the new password is valid
|
||||
password_utils::set_new_password(&password_form.new_password1)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change password request handler. This is used by a user who is already logged in.
|
||||
#[get("/settings/change_password")]
|
||||
pub fn change_password(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Change Password".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("password/change_password", &context)
|
||||
}
|
||||
|
||||
/// Change password form request handler. This route is used by a user who is already logged in.
|
||||
#[post("/settings/change_password", data = "<password_form>")]
|
||||
pub fn change_password_post(password_form: Form<PasswordForm>) -> Template {
|
||||
let result = save_password_form(password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".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("password/change_password", &context)
|
||||
}
|
||||
Err(err) => {
|
||||
let mut context = ChangePasswordContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some(format!("Failed to save new password: {}", err));
|
||||
Template::render("password/change_password", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON change password form request handler.
|
||||
#[post("/api/v1/settings/change_password", data = "<password_form>")]
|
||||
pub fn save_password_form_endpoint(password_form: Json<PasswordForm>) -> Json<JsonResponse> {
|
||||
let result = save_password_form(password_form.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let status = "success".to_string();
|
||||
let msg = "Your password was successfully changed".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = format!("{}", err);
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
279
peach-web/src/routes/device.rs
Normal file
279
peach-web/src/routes/device.rs
Normal file
@ -0,0 +1,279 @@
|
||||
use log::{debug, info, warn};
|
||||
use rocket::{
|
||||
get, post,
|
||||
request::FlashMessage,
|
||||
response::{Flash, Redirect},
|
||||
};
|
||||
use rocket_contrib::{json::Json, templates::Template};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
io,
|
||||
process::{Command, Output},
|
||||
};
|
||||
|
||||
use peach_lib::config_manager::load_peach_config;
|
||||
use peach_lib::stats_client::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat};
|
||||
use peach_lib::{dyndns_client, network_client, oled_client, sbot_client, stats_client};
|
||||
|
||||
use crate::utils::{build_json_response, JsonResponse};
|
||||
|
||||
// HELPERS AND ROUTES FOR /device
|
||||
|
||||
/// System statistics data.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct DeviceContext {
|
||||
pub back: Option<String>,
|
||||
pub cpu_stat_percent: Option<CpuStatPercentages>,
|
||||
pub disk_stats: Vec<DiskUsage>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub load_average: Option<LoadAverage>,
|
||||
pub mem_stats: Option<MemStat>,
|
||||
pub network_ping: String,
|
||||
pub oled_ping: String,
|
||||
pub stats_ping: String,
|
||||
pub dyndns_enabled: bool,
|
||||
pub dyndns_is_online: bool,
|
||||
pub config_is_valid: bool,
|
||||
pub sbot_is_online: bool,
|
||||
pub title: Option<String>,
|
||||
pub uptime: Option<i32>,
|
||||
}
|
||||
|
||||
impl DeviceContext {
|
||||
pub fn build() -> DeviceContext {
|
||||
// convert result to Option<CpuStatPercentages>, discard any error
|
||||
let cpu_stat_percent = stats_client::cpu_stats_percent().ok();
|
||||
let load_average = stats_client::load_average().ok();
|
||||
let mem_stats = stats_client::mem_stats().ok();
|
||||
let network_ping = match network_client::ping() {
|
||||
Ok(_) => "ONLINE".to_string(),
|
||||
Err(_) => "OFFLINE".to_string(),
|
||||
};
|
||||
let oled_ping = match oled_client::ping() {
|
||||
Ok(_) => "ONLINE".to_string(),
|
||||
Err(_) => "OFFLINE".to_string(),
|
||||
};
|
||||
let stats_ping = match stats_client::ping() {
|
||||
Ok(_) => "ONLINE".to_string(),
|
||||
Err(_) => "OFFLINE".to_string(),
|
||||
};
|
||||
let uptime = match stats_client::uptime() {
|
||||
Ok(mins) => mins,
|
||||
Err(_) => "Unavailable".to_string(),
|
||||
};
|
||||
|
||||
// serialize disk usage data into Vec<DiskUsage>
|
||||
let disk_usage_stats = match stats_client::disk_usage() {
|
||||
Ok(disks) => {
|
||||
let partitions: Vec<DiskUsage> = serde_json::from_str(disks.as_str())
|
||||
.expect("Failed to deserialize disk_usage response");
|
||||
partitions
|
||||
}
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
|
||||
let mut disk_stats = Vec::new();
|
||||
// select only the partition we're interested in: /dev/mmcblk0p2 ("/")
|
||||
for disk in disk_usage_stats {
|
||||
if disk.mountpoint == "/" {
|
||||
disk_stats.push(disk);
|
||||
}
|
||||
}
|
||||
|
||||
// parse the uptime string to a signed integer (for math)
|
||||
let uptime_parsed = uptime.parse::<i32>().ok();
|
||||
|
||||
// dyndns_is_online & config_is_valid
|
||||
let dyndns_enabled: bool;
|
||||
let dyndns_is_online: bool;
|
||||
let config_is_valid: bool;
|
||||
let load_peach_config_result = load_peach_config();
|
||||
match load_peach_config_result {
|
||||
Ok(peach_config) => {
|
||||
dyndns_enabled = peach_config.dyn_enabled;
|
||||
config_is_valid = true;
|
||||
if dyndns_enabled {
|
||||
let is_dyndns_online_result = dyndns_client::is_dns_updater_online();
|
||||
match is_dyndns_online_result {
|
||||
Ok(is_online) => {
|
||||
dyndns_is_online = is_online;
|
||||
}
|
||||
Err(_err) => {
|
||||
dyndns_is_online = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dyndns_is_online = false;
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
dyndns_enabled = false;
|
||||
dyndns_is_online = false;
|
||||
config_is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// test if go-sbot is running
|
||||
let sbot_is_online: bool;
|
||||
let sbot_is_online_result = sbot_client::is_sbot_online();
|
||||
match sbot_is_online_result {
|
||||
Ok(val) => {
|
||||
sbot_is_online = val;
|
||||
}
|
||||
Err(_err) => {
|
||||
sbot_is_online = false;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceContext {
|
||||
back: None,
|
||||
cpu_stat_percent,
|
||||
disk_stats,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
load_average,
|
||||
mem_stats,
|
||||
network_ping,
|
||||
oled_ping,
|
||||
stats_ping,
|
||||
dyndns_enabled,
|
||||
dyndns_is_online,
|
||||
config_is_valid,
|
||||
sbot_is_online,
|
||||
title: None,
|
||||
uptime: uptime_parsed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/device")]
|
||||
pub fn device_stats(flash: Option<FlashMessage>) -> Template {
|
||||
// assign context through context_builder call
|
||||
let mut context = DeviceContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Device Status".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("device", &context)
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /device/reboot
|
||||
|
||||
/// Executes a system command to reboot the device immediately.
|
||||
pub fn reboot() -> io::Result<Output> {
|
||||
info!("Rebooting the device");
|
||||
// ideally, we'd like to reboot after 5 seconds to allow time for JSON
|
||||
// response but this is not possible with the `shutdown` command alone.
|
||||
// TODO: send "rebooting..." message to `peach-oled` for display
|
||||
Command::new("sudo")
|
||||
.arg("shutdown")
|
||||
.arg("-r")
|
||||
.arg("now")
|
||||
.output()
|
||||
}
|
||||
|
||||
#[get("/device/reboot")]
|
||||
pub fn reboot_cmd() -> Flash<Redirect> {
|
||||
match reboot() {
|
||||
Ok(_) => Flash::success(Redirect::to("/shutdown"), "Rebooting the device"),
|
||||
Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to reboot the device"),
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON request handler for device reboot.
|
||||
#[post("/api/v1/device/reboot")]
|
||||
pub fn reboot_device() -> Json<JsonResponse> {
|
||||
match reboot() {
|
||||
Ok(_) => {
|
||||
debug!("Going down for reboot...");
|
||||
let status = "success".to_string();
|
||||
let msg = "Going down for reboot.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /device/shutdown
|
||||
|
||||
/// Executes a system command to shutdown the device immediately.
|
||||
pub fn shutdown() -> io::Result<Output> {
|
||||
info!("Shutting down the device");
|
||||
// ideally, we'd like to reboot after 5 seconds to allow time for JSON
|
||||
// response but this is not possible with the `shutdown` command alone.
|
||||
// TODO: send "shutting down..." message to `peach-oled` for display
|
||||
Command::new("sudo").arg("shutdown").arg("now").output()
|
||||
}
|
||||
|
||||
#[get("/device/shutdown")]
|
||||
pub fn shutdown_cmd() -> Flash<Redirect> {
|
||||
match shutdown() {
|
||||
Ok(_) => Flash::success(Redirect::to("/shutdown"), "Shutting down the device"),
|
||||
Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to shutdown the device"),
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown the device
|
||||
#[post("/api/v1/device/shutdown")]
|
||||
pub fn shutdown_device() -> Json<JsonResponse> {
|
||||
match shutdown() {
|
||||
Ok(_) => {
|
||||
debug!("Going down for shutdown...");
|
||||
let status = "success".to_string();
|
||||
let msg = "Going down for shutdown.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /shutdown
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ShutdownContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl ShutdownContext {
|
||||
pub fn build() -> ShutdownContext {
|
||||
ShutdownContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/shutdown")]
|
||||
pub fn shutdown_menu(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ShutdownContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Shutdown Device".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("shutdown", &context)
|
||||
}
|
57
peach-web/src/routes/helpers.rs
Normal file
57
peach-web/src/routes/helpers.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use log::debug;
|
||||
use rocket::{catch, get, response::NamedFile};
|
||||
use rocket_contrib::templates::Template;
|
||||
use serde::Serialize;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[get("/<file..>", rank = 2)]
|
||||
pub fn files(file: PathBuf) -> Option<NamedFile> {
|
||||
NamedFile::open(Path::new("static/").join(file)).ok()
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR 404 ERROR
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ErrorContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl ErrorContext {
|
||||
pub fn build() -> ErrorContext {
|
||||
ErrorContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
pub fn not_found() -> Template {
|
||||
debug!("404 Page Not Found");
|
||||
let mut context = ErrorContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("404: Page Not Found".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some("No resource found for given URL".to_string());
|
||||
|
||||
Template::render("not_found", context)
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR 500 ERROR
|
||||
|
||||
#[catch(500)]
|
||||
pub fn internal_error() -> Template {
|
||||
debug!("500 Internal Server Error");
|
||||
let mut context = ErrorContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("500: Internal Server Error".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some("Internal server error".to_string());
|
||||
|
||||
Template::render("internal_error", context)
|
||||
}
|
67
peach-web/src/routes/index.rs
Normal file
67
peach-web/src/routes/index.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use rocket::{get, request::FlashMessage};
|
||||
use rocket_contrib::templates::Template;
|
||||
use serde::Serialize;
|
||||
|
||||
// HELPERS AND ROUTES FOR / (HOME PAGE)
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct HomeContext {
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl HomeContext {
|
||||
pub fn build() -> HomeContext {
|
||||
HomeContext {
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub fn index() -> Template {
|
||||
let context = HomeContext {
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
};
|
||||
Template::render("index", &context)
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /help
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct HelpContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl HelpContext {
|
||||
pub fn build() -> HelpContext {
|
||||
HelpContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/help")]
|
||||
pub fn help(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = HelpContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Help".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("help", &context)
|
||||
}
|
7
peach-web/src/routes/mod.rs
Normal file
7
peach-web/src/routes/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod authentication;
|
||||
pub mod device;
|
||||
pub mod helpers;
|
||||
pub mod index;
|
||||
pub mod ping;
|
||||
pub mod scuttlebutt;
|
||||
pub mod settings;
|
86
peach-web/src/routes/ping.rs
Normal file
86
peach-web/src/routes/ping.rs
Normal file
@ -0,0 +1,86 @@
|
||||
//! Helper routes for pinging services to check that they are active
|
||||
use log::{debug, warn};
|
||||
use rocket::get;
|
||||
use rocket_contrib::json::Json;
|
||||
|
||||
use peach_lib::dyndns_client::is_dns_updater_online;
|
||||
use peach_lib::network_client;
|
||||
use peach_lib::oled_client;
|
||||
use peach_lib::stats_client;
|
||||
|
||||
use crate::utils::{build_json_response, JsonResponse};
|
||||
|
||||
/// Status route: useful for checking connectivity from web client.
|
||||
#[get("/api/v1/ping")]
|
||||
pub fn ping_pong() -> Json<JsonResponse> {
|
||||
// ping pong
|
||||
let status = "success".to_string();
|
||||
let msg = "pong!".to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
|
||||
/// Test route: useful for ad hoc testing.
|
||||
#[get("/api/v1/test")]
|
||||
pub fn test_route() -> Json<JsonResponse> {
|
||||
let val = is_dns_updater_online().unwrap();
|
||||
let status = "success".to_string();
|
||||
let msg = val.to_string();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
|
||||
/// Status route: check availability of `peach-network` microservice.
|
||||
#[get("/api/v1/ping/network")]
|
||||
pub fn ping_network() -> Json<JsonResponse> {
|
||||
match network_client::ping() {
|
||||
Ok(_) => {
|
||||
debug!("peach-network responded successfully");
|
||||
let status = "success".to_string();
|
||||
let msg = "peach-network is available.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Status route: check availability of `peach-oled` microservice.
|
||||
#[get("/api/v1/ping/oled")]
|
||||
pub fn ping_oled() -> Json<JsonResponse> {
|
||||
match oled_client::ping() {
|
||||
Ok(_) => {
|
||||
debug!("peach-oled responded successfully");
|
||||
let status = "success".to_string();
|
||||
let msg = "peach-oled is available.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Status route: check availability of `peach-stats` microservice.
|
||||
#[get("/api/v1/ping/stats")]
|
||||
pub fn ping_stats() -> Json<JsonResponse> {
|
||||
match stats_client::ping() {
|
||||
Ok(_) => {
|
||||
debug!("peach-stats responded successfully");
|
||||
let status = "success".to_string();
|
||||
let msg = "peach-stats is available.".to_string();
|
||||
Json(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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
110
peach-web/src/routes/scuttlebutt.rs
Normal file
110
peach-web/src/routes/scuttlebutt.rs
Normal file
@ -0,0 +1,110 @@
|
||||
//! Routes for ScuttleButt related functionality.
|
||||
|
||||
use rocket::{get, request::FlashMessage};
|
||||
use rocket_contrib::templates::Template;
|
||||
use serde::Serialize;
|
||||
|
||||
// HELPERS AND ROUTES FOR /messages
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MessageContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl MessageContext {
|
||||
pub fn build() -> MessageContext {
|
||||
MessageContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/messages")]
|
||||
pub fn messages(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = MessageContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Private Messages".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("messages", &context)
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /peers
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PeerContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl PeerContext {
|
||||
pub fn build() -> PeerContext {
|
||||
PeerContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/peers")]
|
||||
pub fn peers(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = PeerContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Scuttlebutt Peers".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("peers", &context)
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /profile
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ProfileContext {
|
||||
pub back: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl ProfileContext {
|
||||
pub fn build() -> ProfileContext {
|
||||
ProfileContext {
|
||||
back: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/profile")]
|
||||
pub fn profile(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ProfileContext::build();
|
||||
context.back = Some("/".to_string());
|
||||
context.title = Some("Profile".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("profile", &context)
|
||||
}
|
128
peach-web/src/routes/settings/admin.rs
Normal file
128
peach-web/src/routes/settings/admin.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use rocket::{
|
||||
get, post,
|
||||
request::{FlashMessage, Form, FromForm},
|
||||
response::{Flash, Redirect},
|
||||
uri,
|
||||
};
|
||||
use rocket_contrib::templates::Template;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use peach_lib::config_manager;
|
||||
use peach_lib::config_manager::load_peach_config;
|
||||
|
||||
use crate::error::PeachWebError;
|
||||
|
||||
// HELPERS AND ROUTES FOR /settings/configure_admin
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ConfigureAdminContext {
|
||||
pub ssb_admin_ids: Vec<String>,
|
||||
pub back: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
impl ConfigureAdminContext {
|
||||
pub fn build() -> ConfigureAdminContext {
|
||||
let peach_config = load_peach_config().unwrap();
|
||||
let ssb_admin_ids = peach_config.ssb_admin_ids;
|
||||
ConfigureAdminContext {
|
||||
ssb_admin_ids,
|
||||
back: None,
|
||||
title: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// View and delete currently configured admin.
|
||||
#[get("/settings/configure_admin")]
|
||||
pub fn configure_admin(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ConfigureAdminContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure Admin".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("admin/configure_admin", &context)
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /settings/admin/add
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct AddAdminForm {
|
||||
pub ssb_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AddAdminContext {
|
||||
pub back: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
||||
|
||||
impl AddAdminContext {
|
||||
pub fn build() -> AddAdminContext {
|
||||
AddAdminContext {
|
||||
back: None,
|
||||
title: None,
|
||||
flash_name: None,
|
||||
flash_msg: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> {
|
||||
let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?;
|
||||
// if the previous line didn't throw an error then it was a success
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[get("/settings/admin/add")]
|
||||
pub fn add_admin(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = AddAdminContext::build();
|
||||
context.back = Some("/settings/configure_admin".to_string());
|
||||
context.title = Some("Add Admin".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
// template_dir is set in Rocket.toml
|
||||
Template::render("admin/add_admin", &context)
|
||||
}
|
||||
|
||||
#[post("/settings/admin/add", data = "<add_admin_form>")]
|
||||
pub fn add_admin_post(add_admin_form: Form<AddAdminForm>) -> Flash<Redirect> {
|
||||
let result = save_add_admin_form(add_admin_form.into_inner());
|
||||
let url = uri!(configure_admin);
|
||||
match result {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "Successfully added new admin"),
|
||||
Err(_) => Flash::error(Redirect::to(url), "Failed to add new admin"),
|
||||
}
|
||||
}
|
||||
|
||||
// HELPERS AND ROUTES FOR /settings/admin/delete
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct DeleteAdminForm {
|
||||
pub ssb_id: String,
|
||||
}
|
||||
|
||||
#[post("/settings/admin/delete", data = "<delete_admin_form>")]
|
||||
pub fn delete_admin_post(delete_admin_form: Form<DeleteAdminForm>) -> Flash<Redirect> {
|
||||
let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id);
|
||||
let url = uri!(configure_admin);
|
||||
match result {
|
||||
Ok(_) => Flash::success(Redirect::to(url), "Successfully removed admin id"),
|
||||
Err(_) => Flash::error(Redirect::to(url), "Failed to remove admin id"),
|
||||
}
|
||||
}
|
167
peach-web/src/routes/settings/dns.rs
Normal file
167
peach-web/src/routes/settings/dns.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use log::info;
|
||||
use rocket::{
|
||||
get, post,
|
||||
request::{FlashMessage, Form, FromForm},
|
||||
};
|
||||
use rocket_contrib::{json::Json, templates::Template};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use peach_lib::config_manager;
|
||||
use peach_lib::config_manager::load_peach_config;
|
||||
use peach_lib::dyndns_client;
|
||||
use peach_lib::dyndns_client::{
|
||||
check_is_new_dyndns_domain, get_dyndns_subdomain, get_full_dynamic_domain,
|
||||
is_dns_updater_online,
|
||||
};
|
||||
use peach_lib::error::PeachError;
|
||||
use peach_lib::jsonrpc_client_core::{Error, ErrorKind};
|
||||
use peach_lib::jsonrpc_core::types::error::ErrorCode;
|
||||
|
||||
use crate::error::PeachWebError;
|
||||
use crate::utils::{build_json_response, JsonResponse};
|
||||
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct DnsForm {
|
||||
pub external_domain: String,
|
||||
pub enable_dyndns: bool,
|
||||
pub dynamic_domain: String,
|
||||
}
|
||||
|
||||
pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> {
|
||||
// first save local configurations
|
||||
config_manager::set_external_domain(&dns_form.external_domain)?;
|
||||
config_manager::set_dyndns_enabled_value(dns_form.enable_dyndns)?;
|
||||
// if dynamic dns is enabled and this is a new domain name, then register it
|
||||
if dns_form.enable_dyndns {
|
||||
let full_dynamic_domain = get_full_dynamic_domain(&dns_form.dynamic_domain);
|
||||
// check if this is a new domain or if its already registered
|
||||
let is_new_domain = check_is_new_dyndns_domain(&full_dynamic_domain);
|
||||
if is_new_domain {
|
||||
match dyndns_client::register_domain(&full_dynamic_domain) {
|
||||
Ok(_) => {
|
||||
info!("Registered new dyndns domain");
|
||||
// successful update
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Failed to register dyndns domain: {:?}", err);
|
||||
// json response for failed update
|
||||
let msg: String = match err {
|
||||
PeachError::JsonRpcClientCore { source } => {
|
||||
match source {
|
||||
Error(ErrorKind::JsonRpcError(err), _state) => match err.code {
|
||||
ErrorCode::ServerError(-32030) => {
|
||||
format!("Error registering domain: {} was previously registered", full_dynamic_domain)
|
||||
}
|
||||
_ => {
|
||||
format!("Failed to register dyndns domain {:?}", err)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
format!("Failed to register dyndns domain: {:?}", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => "Failed to register dyndns domain".to_string(),
|
||||
};
|
||||
Err(PeachWebError::FailedToRegisterDynDomain { msg })
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the domain is already registered, then dont re-register, and just return success
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[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("/network/dns")]
|
||||
pub fn configure_dns(flash: Option<FlashMessage>) -> Template {
|
||||
let mut context = ConfigureDNSContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
// check to see if there is a flash message to display
|
||||
if let Some(flash) = flash {
|
||||
// add flash message contents to the context object
|
||||
context.flash_name = Some(flash.name().to_string());
|
||||
context.flash_msg = Some(flash.msg().to_string());
|
||||
};
|
||||
Template::render("configure_dns", &context)
|
||||
}
|
||||
|
||||
#[post("/network/dns", data = "<dns>")]
|
||||
pub fn configure_dns_post(dns: Form<DnsForm>) -> Template {
|
||||
let result = save_dns_configuration(dns.into_inner());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut context = ConfigureDNSContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
context.flash_name = Some("success".to_string());
|
||||
context.flash_msg = Some("New dynamic dns configuration is now enabled".to_string());
|
||||
Template::render("configure_dns", &context)
|
||||
}
|
||||
Err(err) => {
|
||||
let mut context = ConfigureDNSContext::build();
|
||||
// set back icon link to network route
|
||||
context.back = Some("/network".to_string());
|
||||
context.title = Some("Configure DNS".to_string());
|
||||
context.flash_name = Some("error".to_string());
|
||||
context.flash_msg = Some(format!("Failed to save dns configurations: {}", err));
|
||||
Template::render("configure_dns", &context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/dns/configure", data = "<dns_form>")]
|
||||
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>) -> Json<JsonResponse> {
|
||||
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();
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
Err(err) => {
|
||||
let status = "error".to_string();
|
||||
let msg = format!("{}", err);
|
||||
Json(build_json_response(status, None, Some(msg)))
|
||||
}
|
||||
}
|
||||
}
|
3
peach-web/src/routes/settings/mod.rs
Normal file
3
peach-web/src/routes/settings/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod admin;
|
||||
pub mod dns;
|
||||
pub mod network;
|
1065
peach-web/src/routes/settings/network.rs
Normal file
1065
peach-web/src/routes/settings/network.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,9 @@ use rocket::http::{ContentType, Status};
|
||||
use rocket::local::Client;
|
||||
use rocket_contrib::json;
|
||||
|
||||
use crate::utils::build_json_response;
|
||||
|
||||
use super::rocket;
|
||||
use crate::json_api::build_json_response;
|
||||
|
||||
// helper function to test correct retrieval and content of a file
|
||||
fn test_query_file<T>(path: &str, file: T, status: Status)
|
||||
|
29
peach-web/src/utils.rs
Normal file
29
peach-web/src/utils.rs
Normal file
@ -0,0 +1,29 @@
|
||||
pub mod monitor;
|
||||
|
||||
use rocket_contrib::json::JsonValue;
|
||||
use serde::Serialize;
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct JsonResponse {
|
||||
pub status: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<JsonValue>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub msg: Option<String>,
|
||||
}
|
||||
|
||||
pub fn build_json_response(
|
||||
status: String,
|
||||
data: Option<JsonValue>,
|
||||
msg: Option<String>,
|
||||
) -> JsonResponse {
|
||||
JsonResponse { status, data, msg }
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FlashContext {
|
||||
pub flash_name: Option<String>,
|
||||
pub flash_msg: Option<String>,
|
||||
}
|
@ -9,12 +9,12 @@ use websocket::sync::Server;
|
||||
use websocket::{Message, OwnedMessage};
|
||||
|
||||
pub fn websocket_server(address: String) -> io::Result<()> {
|
||||
// Start listening for WebSocket connections
|
||||
// start listening for WebSocket connections
|
||||
let ws_server = Server::bind(address)?;
|
||||
|
||||
info!("Listening for WebSocket connections.");
|
||||
for connection in ws_server.filter_map(Result::ok) {
|
||||
// Spawn a new thread for each connection.
|
||||
// spawn a new thread for each connection
|
||||
thread::spawn(move || {
|
||||
if !connection
|
||||
.protocols()
|
||||
|
Loading…
Reference in New Issue
Block a user