peach-workspace/peach-web/src/routes/status/device.rs

238 lines
7.6 KiB
Rust

use log::info;
use rocket::{
get,
request::FlashMessage,
response::{Flash, Redirect},
};
use rocket_dyn_templates::Template;
use serde::Serialize;
use std::{
io,
process::{Command, Output},
};
use peach_lib::{
config_manager::load_peach_config, dyndns_client, network_client, oled_client, sbot_client,
};
use peach_stats::{
stats,
stats::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat},
};
use crate::routes::authentication::Authenticated;
// HELPERS AND ROUTES FOR /status
/// System statistics data.
#[derive(Debug, Serialize)]
pub struct StatusContext {
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 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 StatusContext {
pub fn build() -> StatusContext {
// convert result to Option<CpuStatPercentages>, discard any error
let cpu_stat_percent = stats::cpu_stats_percent().ok();
let load_average = stats::load_average().ok();
let mem_stats = stats::mem_stats().ok();
// TODO: add `wpa_supplicant_status` to peach_network to replace this ping call
// instead of: "is the network json-rpc server running?", we want to ask:
// "is the wpa_supplicant systemd service functioning correctly?"
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 uptime = match stats::uptime() {
Ok(secs) => {
let uptime_mins = secs / 60;
uptime_mins.to_string()
}
Err(_) => "Unavailable".to_string(),
};
// parse the uptime string to a signed integer (for math)
let uptime_parsed = uptime.parse::<i32>().ok();
// serialize disk usage data into Vec<DiskUsage>
let disk_usage_stats = match stats::disk_usage() {
Ok(disks) => disks,
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);
}
}
// 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_result = sbot_client::is_sbot_online();
let sbot_is_online: bool = match sbot_is_online_result {
Ok(val) => val,
Err(_err) => false,
};
StatusContext {
back: None,
cpu_stat_percent,
disk_stats,
flash_name: None,
flash_msg: None,
load_average,
mem_stats,
network_ping,
oled_ping,
dyndns_enabled,
dyndns_is_online,
config_is_valid,
sbot_is_online,
title: None,
uptime: uptime_parsed,
}
}
}
#[get("/")]
pub fn device_status(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
// assign context through context_builder call
let mut context = StatusContext::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.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("status/device", &context)
}
// HELPERS AND ROUTES FOR /power/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("/power/reboot")]
pub fn reboot_cmd(_auth: Authenticated) -> Flash<Redirect> {
match reboot() {
Ok(_) => Flash::success(Redirect::to("/power"), "Rebooting the device"),
Err(_) => Flash::error(Redirect::to("/power"), "Failed to reboot the device"),
}
}
// HELPERS AND ROUTES FOR /power/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("/power/shutdown")]
pub fn shutdown_cmd(_auth: Authenticated) -> Flash<Redirect> {
match shutdown() {
Ok(_) => Flash::success(Redirect::to("/power"), "Shutting down the device"),
Err(_) => Flash::error(Redirect::to("/power"), "Failed to shutdown the device"),
}
}
// HELPERS AND ROUTES FOR /power
#[derive(Debug, Serialize)]
pub struct PowerContext {
pub back: Option<String>,
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
pub title: Option<String>,
}
impl PowerContext {
pub fn build() -> PowerContext {
PowerContext {
back: None,
flash_name: None,
flash_msg: None,
title: None,
}
}
}
#[get("/power")]
pub fn power_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = PowerContext::build();
context.back = Some("/".to_string());
context.title = Some("Power Menu".to_string());
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("power", &context)
}