Refactor routes into functional groupings #12
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::error::PeachError;
|
||||||
use peach_lib::{serde_json, serde_yaml};
|
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.
|
//! of the template to be rendered.
|
||||||
|
|
||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
#![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 error;
|
||||||
pub mod forms;
|
|
||||||
pub mod json_api;
|
|
||||||
pub mod monitor;
|
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
pub mod utils;
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
use std::{env, thread};
|
use std::{env, thread};
|
||||||
|
@ -47,8 +39,16 @@ use log::{debug, error, info};
|
||||||
use rocket::{catchers, routes};
|
use rocket::{catchers, routes};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
|
|
||||||
use crate::json_api::*;
|
use crate::routes::authentication::*;
|
||||||
use crate::routes::*;
|
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::*;
|
use crate::ws::*;
|
||||||
|
|
||||||
pub type BoxError = Box<dyn std::error::Error>;
|
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::local::Client;
|
||||||
use rocket_contrib::json;
|
use rocket_contrib::json;
|
||||||
|
|
||||||
|
use crate::utils::build_json_response;
|
||||||
|
|
||||||
use super::rocket;
|
use super::rocket;
|
||||||
use crate::json_api::build_json_response;
|
|
||||||
|
|
||||||
// helper function to test correct retrieval and content of a file
|
// helper function to test correct retrieval and content of a file
|
||||||
fn test_query_file<T>(path: &str, file: T, status: Status)
|
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};
|
use websocket::{Message, OwnedMessage};
|
||||||
|
|
||||||
pub fn websocket_server(address: String) -> io::Result<()> {
|
pub fn websocket_server(address: String) -> io::Result<()> {
|
||||||
// Start listening for WebSocket connections
|
// start listening for WebSocket connections
|
||||||
let ws_server = Server::bind(address)?;
|
let ws_server = Server::bind(address)?;
|
||||||
|
|
||||||
info!("Listening for WebSocket connections.");
|
info!("Listening for WebSocket connections.");
|
||||||
for connection in ws_server.filter_map(Result::ok) {
|
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 || {
|
thread::spawn(move || {
|
||||||
if !connection
|
if !connection
|
||||||
.protocols()
|
.protocols()
|
||||||
|
|
Loading…
Reference in New Issue