Merge pull request 'Refactor routes into functional groupings' (#12) from routes-refactor into main
Reviewed-on: #12
This commit is contained in:
commit
1aa5821adf
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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)
|
||||
}
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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;
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"),
|
||||
}
|
||||
}
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod admin;
|
||||
pub mod dns;
|
||||
pub mod network;
|
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)
|
||||
|
|
|
@ -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