resolve merge conflicts

This commit is contained in:
glyph 2021-12-13 08:26:25 +02:00
commit 8032b83c41
43 changed files with 1101 additions and 1440 deletions

582
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ members = [
"peach-menu", "peach-menu",
"peach-monitor", "peach-monitor",
"peach-stats", "peach-stats",
"peach-jsonrpc-server",
"peach-probe", "peach-probe",
"peach-dyndns-updater" "peach-dyndns-updater"
] ]

View File

@ -0,0 +1,25 @@
[package]
name = "peach-jsonrpc-server"
authors = ["Andrew Reid <glyph@mycelial.technology>"]
version = "0.1.0"
edition = "2021"
description = "JSON-RPC over HTTP for the PeachCloud system. Provides a JSON-RPC wrapper around the stats, network and oled libraries."
homepage = "https://opencollective.com/peachcloud"
repository = "https://git.coopcloud.tech/PeachCloud/peach-workspace"
readme = "README.md"
license = "AGPL-3.0-only"
publish = false
[badges]
maintenance = { status = "actively-developed" }
[dependencies]
env_logger = "0.9"
jsonrpc-core = "18"
jsonrpc-http-server = "18"
log = "0.4"
miniserde = "0.1.15"
peach-stats = { path = "../peach-stats", features = ["miniserde_support"] }
[dev-dependencies]
jsonrpc-test = "18"

View File

@ -0,0 +1,72 @@
# peach-jsonrpc-server
A JSON-RPC server for the PeachCloud system which exposes an API over HTTP.
Currently includes peach-stats capability (system statistics).
## JSON-RPC API
| Method | Description | Returns |
| --- | --- | --- |
| `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
| `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` |
| `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` |
| `load_average` | Load average statistics | `one`, `five`, `fifteen` |
| `mem_stats` | Memory statistics | `total`, `free`, `used` |
| `ping` | Microservice status | `success` if running |
| `uptime` | System uptime | `secs` |
## Environment
The JSON-RPC HTTP server is currently hardcoded to run on "127.0.0.1:5110". Address and port configuration settings will later be exposed via CLI arguments and possibly an environment variable.
Logging is made available with `env_logger`:
`export RUST_LOG=info`
Other logging levels include `debug`, `warn` and `error`.
## Setup
Clone the peach-workspace repo:
`git clone https://git.coopcloud.tech/PeachCloud/peach-workspace`
Move into the repo peaach-jsonrpc-server directory and compile a release build:
`cd peach-jsonrpc-server`
`cargo build --release`
Run the binary:
`./peach-workspace/target/release/peach-jsonrpc-server`
## Debian Packaging
TODO.
## Example Usage
**Get CPU Statistics**
With microservice running, open a second terminal window and use `curl` to call server methods:
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "cpu_stats", "id":1 }' 127.0.0.1:5110`
Server responds with:
`{"jsonrpc":"2.0","result":"{\"user\":4661083,\"system\":1240371,\"idle\":326838290,\"nice\":0}","id":1}`
**Get System Uptime**
With microservice running, open a second terminal window and use `curl` to call server methods:
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "uptime", "id":1 }' 127.0.0.1:5110`
Server responds with:
`{"jsonrpc":"2.0","result":"{\"secs\":840968}","id":1}`
### Licensing
AGPL-3.0

View File

@ -0,0 +1,46 @@
use std::fmt;
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
use peach_stats::StatsError;
/// Custom error type encapsulating all possible errors for a JSON-RPC server
/// and associated methods.
#[derive(Debug)]
pub enum JsonRpcServerError {
/// An error returned from the `peach-stats` library.
Stats(StatsError),
/// An expected JSON-RPC method parameter was not provided.
MissingParameter(JsonRpcError),
/// Failed to parse a provided JSON-RPC method parameter.
ParseParameter(JsonRpcError),
}
impl fmt::Display for JsonRpcServerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
JsonRpcServerError::ParseParameter(ref source) => {
write!(f, "Failed to parse parameter: {}", source)
}
JsonRpcServerError::MissingParameter(ref source) => {
write!(f, "Missing expected parameter: {}", source)
}
JsonRpcServerError::Stats(ref source) => {
write!(f, "{}", source)
}
}
}
}
impl From<JsonRpcServerError> for JsonRpcError {
fn from(err: JsonRpcServerError) -> Self {
match &err {
JsonRpcServerError::Stats(source) => JsonRpcError {
code: ErrorCode::ServerError(-32001),
message: format!("{}", source),
data: None,
},
JsonRpcServerError::MissingParameter(source) => source.clone(),
JsonRpcServerError::ParseParameter(source) => source.clone(),
}
}
}

View File

@ -0,0 +1,141 @@
//! # peach-jsonrpc-server
//!
//! A JSON-RPC server which exposes an API over HTTP.
use std::env;
use std::result::Result;
use jsonrpc_core::{IoHandler, Value};
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
use log::info;
use miniserde::json;
use peach_stats::stats;
mod error;
use crate::error::JsonRpcServerError;
/// Create JSON-RPC I/O handler, add RPC methods and launch HTTP server.
pub fn run() -> Result<(), JsonRpcServerError> {
info!("Starting up.");
info!("Creating JSON-RPC I/O handler.");
let mut io = IoHandler::default();
io.add_sync_method("ping", |_| Ok(Value::String("success".to_string())));
// TODO: add blocks of methods according to provided flags
/* PEACH-STATS RPC METHODS */
io.add_sync_method("cpu_stats", move |_| {
info!("Fetching CPU statistics.");
let cpu = stats::cpu_stats().map_err(JsonRpcServerError::Stats)?;
let json_cpu = json::to_string(&cpu);
Ok(Value::String(json_cpu))
});
io.add_sync_method("cpu_stats_percent", move |_| {
info!("Fetching CPU statistics as percentages.");
let cpu = stats::cpu_stats_percent().map_err(JsonRpcServerError::Stats)?;
let json_cpu = json::to_string(&cpu);
Ok(Value::String(json_cpu))
});
io.add_sync_method("disk_usage", move |_| {
info!("Fetching disk usage statistics.");
let disks = stats::disk_usage().map_err(JsonRpcServerError::Stats)?;
let json_disks = json::to_string(&disks);
Ok(Value::String(json_disks))
});
io.add_sync_method("load_average", move |_| {
info!("Fetching system load average statistics.");
let avg = stats::load_average().map_err(JsonRpcServerError::Stats)?;
let json_avg = json::to_string(&avg);
Ok(Value::String(json_avg))
});
io.add_sync_method("mem_stats", move |_| {
info!("Fetching current memory statistics.");
let mem = stats::mem_stats().map_err(JsonRpcServerError::Stats)?;
let json_mem = json::to_string(&mem);
Ok(Value::String(json_mem))
});
io.add_sync_method("uptime", move |_| {
info!("Fetching system uptime.");
let uptime = stats::uptime().map_err(JsonRpcServerError::Stats)?;
let json_uptime = json::to_string(&uptime);
Ok(Value::String(json_uptime))
});
let http_server =
env::var("PEACH_JSONRPC_SERVER").unwrap_or_else(|_| "127.0.0.1:5110".to_string());
info!("Starting JSON-RPC server on {}.", http_server);
let server = ServerBuilder::new(io)
.cors(DomainsValidation::AllowOnly(vec![
AccessControlAllowOrigin::Null,
]))
.start_http(
&http_server
.parse()
.expect("Invalid HTTP address and port combination"),
)
.expect("Unable to start RPC server");
server.wait();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
use jsonrpc_test as test_rpc;
#[test]
fn rpc_success() {
let rpc = {
let mut io = IoHandler::new();
io.add_sync_method("rpc_success_response", |_| {
Ok(Value::String("success".into()))
});
test_rpc::Rpc::from(io)
};
assert_eq!(rpc.request("rpc_success_response", &()), r#""success""#);
}
#[test]
fn rpc_parse_error() {
let rpc = {
let mut io = IoHandler::new();
io.add_sync_method("rpc_parse_error", |_| {
let e = JsonRpcError {
code: ErrorCode::ParseError,
message: String::from("Parse error"),
data: None,
};
Err(JsonRpcError::from(JsonRpcServerError::MissingParameter(e)))
});
test_rpc::Rpc::from(io)
};
assert_eq!(
rpc.request("rpc_parse_error", &()),
r#"{
"code": -32700,
"message": "Parse error"
}"#
);
}
}

View File

@ -0,0 +1,34 @@
#![warn(missing_docs)]
//! # peach-jsonrpc-server
//!
//! A JSON-RPC server which exposes an over HTTP.
//!
//! Currently includes peach-stats capability (system statistics).
//!
//! ## API
//!
//! | Method | Description | Returns |
//! | --- | --- | --- |
//! | `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
//! | `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` |
//! | `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` |
//! | `load_average` | Load average statistics | `one`, `five`, `fifteen` |
//! | `mem_stats` | Memory statistics | `total`, `free`, `used` |
//! | `ping` | Microservice status | `success` if running |
//! | `uptime` | System uptime | `secs` |
use std::process;
use log::error;
fn main() {
// initalize the logger
env_logger::init();
// handle errors returned from `run`
if let Err(e) = peach_jsonrpc_server::run() {
error!("Application error: {}", e);
process::exit(1);
}
}

View File

@ -6,7 +6,6 @@ edition = "2018"
[dependencies] [dependencies]
chrono = "0.4.19" chrono = "0.4.19"
env_logger = "0.6"
fslock="0.1.6" fslock="0.1.6"
jsonrpc-client-core = "0.5" jsonrpc-client-core = "0.5"
jsonrpc-client-http = "0.5" jsonrpc-client-http = "0.5"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-oled" name = "peach-oled"
version = "0.1.3" version = "0.1.4"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018" edition = "2018"
description = "Write and draw to OLED display using JSON-RPC over HTTP." description = "Write and draw to OLED display using JSON-RPC over HTTP."
@ -27,18 +27,16 @@ travis-ci = { repository = "peachcloud/peach-oled", branch = "master" }
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
jsonrpc-core = "11.0.0"
jsonrpc-http-server = "11.0.0"
linux-embedded-hal = "0.2.2"
embedded-graphics = "0.4.7" embedded-graphics = "0.4.7"
tinybmp = "0.1.0" env_logger = "0.9"
ssd1306 = "0.2.6" jsonrpc-core = "18"
serde = { version = "1.0.87", features = ["derive"] } jsonrpc-http-server = "18"
serde_json = "1.0.39" linux-embedded-hal = "0.2.2"
log = "0.4.0" log = "0.4"
env_logger = "0.6.1" serde = { version = "1", features = ["derive"] }
snafu = "0.4.1"
nix="0.11" nix="0.11"
ssd1306 = "0.2.6"
tinybmp = "0.1.0"
[dev-dependencies] [dev-dependencies]
jsonrpc-test = "11.0.0" jsonrpc-test = "18"

View File

@ -1,6 +1,6 @@
# peach-oled # peach-oled
[![Build Status](https://travis-ci.com/peachcloud/peach-oled.svg?branch=master)](https://travis-ci.com/peachcloud/peach-oled) ![Generic badge](https://img.shields.io/badge/version-0.1.3-<COLOR>.svg) [![Build Status](https://travis-ci.com/peachcloud/peach-oled.svg?branch=master)](https://travis-ci.com/peachcloud/peach-oled) ![Generic badge](https://img.shields.io/badge/version-0.1.4-<COLOR>.svg)
OLED microservice module for PeachCloud. Write to a 128x64 OLED display with SDD1306 driver (I2C) using [JSON-RPC](https://www.jsonrpc.org/specification) over http. OLED microservice module for PeachCloud. Write to a 128x64 OLED display with SDD1306 driver (I2C) using [JSON-RPC](https://www.jsonrpc.org/specification) over http.

View File

@ -1,44 +1,68 @@
use std::error; use std::{error, fmt};
use jsonrpc_core::{types::error::Error, ErrorCode}; use jsonrpc_core::types::error::Error as JsonRpcError;
use linux_embedded_hal as hal; use jsonrpc_core::ErrorCode;
use snafu::Snafu; use linux_embedded_hal::i2cdev::linux::LinuxI2CError;
pub type BoxError = Box<dyn error::Error>; #[derive(Debug)]
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum OledError { pub enum OledError {
#[snafu(display("Failed to create interface for I2C device: {}", source))]
I2CError { I2CError {
source: hal::i2cdev::linux::LinuxI2CError, source: LinuxI2CError,
}, },
#[snafu(display("Coordinate {} out of range {}: {}", coord, range, value))]
InvalidCoordinate { InvalidCoordinate {
coord: String, coord: String,
range: String, range: String,
value: i32, value: i32,
}, },
InvalidFontSize {
// TODO: implement for validate() in src/lib.rs font: String,
#[snafu(display("Font size invalid: {}", font))] },
InvalidFontSize { font: String }, InvalidString {
len: usize,
#[snafu(display("String length out of range 0-21: {}", len))] },
InvalidString { len: usize }, MissingParameter {
source: JsonRpcError,
#[snafu(display("Missing expected parameter: {}", e))] },
MissingParameter { e: Error }, ParseError {
source: JsonRpcError,
#[snafu(display("Failed to parse parameter: {}", e))] },
ParseError { e: Error },
} }
impl From<OledError> for Error { impl error::Error for OledError {}
impl fmt::Display for OledError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
OledError::ParseError { ref source } => {
write!(f, "Failed to parse parameter: {}", source)
}
OledError::MissingParameter { ref source } => {
write!(f, "Missing expected parameter: {}", source)
}
OledError::InvalidString { len } => {
write!(f, "String length out of range 0-21: {}", len)
}
OledError::InvalidFontSize { ref font } => {
write!(f, "Invalid font size: {}", font)
}
OledError::InvalidCoordinate {
ref coord,
ref range,
value,
} => {
write!(f, "Coordinate {} out of range {}: {}", coord, range, value)
}
OledError::I2CError { ref source } => {
write!(f, "Failed to create interface for I2C device: {}", source)
}
}
}
}
impl From<OledError> for JsonRpcError {
fn from(err: OledError) -> Self { fn from(err: OledError) -> Self {
match &err { match &err {
OledError::I2CError { source } => Error { OledError::I2CError { source } => JsonRpcError {
code: ErrorCode::ServerError(-32000), code: ErrorCode::ServerError(-32000),
message: format!("Failed to create interface for I2C device: {}", source), message: format!("Failed to create interface for I2C device: {}", source),
data: None, data: None,
@ -47,7 +71,7 @@ impl From<OledError> for Error {
coord, coord,
value, value,
range, range,
} => Error { } => JsonRpcError {
code: ErrorCode::ServerError(-32001), code: ErrorCode::ServerError(-32001),
message: format!( message: format!(
"Validation error: coordinate {} out of range {}: {}", "Validation error: coordinate {} out of range {}: {}",
@ -55,18 +79,18 @@ impl From<OledError> for Error {
), ),
data: None, data: None,
}, },
OledError::InvalidFontSize { font } => Error { OledError::InvalidFontSize { font } => JsonRpcError {
code: ErrorCode::ServerError(-32002), code: ErrorCode::ServerError(-32002),
message: format!("Validation error: {} is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead", font), message: format!("Validation error: {} is not an accepted font size. Use 6x8, 6x12, 8x16 or 12x16 instead", font),
data: None, data: None,
}, },
OledError::InvalidString { len } => Error { OledError::InvalidString { len } => JsonRpcError {
code: ErrorCode::ServerError(-32003), code: ErrorCode::ServerError(-32003),
message: format!("Validation error: string length {} out of range 0-21", len), message: format!("Validation error: string length {} out of range 0-21", len),
data: None, data: None,
}, },
OledError::MissingParameter { e } => e.clone(), OledError::MissingParameter { source } => source.clone(),
OledError::ParseError { e } => e.clone(), OledError::ParseError { source } => source.clone(),
} }
} }
} }

View File

@ -6,23 +6,23 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use embedded_graphics::coord::Coord; use embedded_graphics::{
use embedded_graphics::fonts::{Font12x16, Font6x12, Font6x8, Font8x16}; coord::Coord,
use embedded_graphics::image::Image1BPP; fonts::{Font12x16, Font6x12, Font6x8, Font8x16},
use embedded_graphics::prelude::*; image::Image1BPP,
prelude::*,
};
use hal::I2cdev; use hal::I2cdev;
use jsonrpc_core::{types::error::Error, IoHandler, Params, Value}; use jsonrpc_core::{types::error::Error, IoHandler, Params, Value};
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
use linux_embedded_hal as hal; use linux_embedded_hal as hal;
use log::{debug, error, info}; use log::{debug, error, info};
use serde::Deserialize; use serde::Deserialize;
use snafu::{ensure, ResultExt}; use ssd1306::{prelude::*, Builder};
use ssd1306::prelude::*;
use ssd1306::Builder;
use crate::error::{BoxError, I2CError, InvalidCoordinate, InvalidString, OledError}; use crate::error::OledError;
//define the Graphic struct for receiving draw commands // define the Graphic struct for receiving draw commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Graphic { pub struct Graphic {
bytes: Vec<u8>, bytes: Vec<u8>,
@ -32,7 +32,7 @@ pub struct Graphic {
y_coord: i32, y_coord: i32,
} }
//define the Msg struct for receiving write commands // define the Msg struct for receiving write commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Msg { pub struct Msg {
x_coord: i32, x_coord: i32,
@ -41,86 +41,61 @@ pub struct Msg {
font_size: String, font_size: String,
} }
//definte the On struct for receiving power on/off commands // definte the On struct for receiving power on/off commands
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct On { pub struct On {
on: bool, on: bool,
} }
fn validate(m: &Msg) -> Result<(), OledError> { fn validate(msg: &Msg) -> Result<(), OledError> {
ensure!( if msg.string.len() > 21 {
m.string.len() <= 21, Err(OledError::InvalidString {
InvalidString { len: msg.string.len(),
len: m.string.len() })
} } else if msg.x_coord < 0 || msg.x_coord > 128 {
); Err(OledError::InvalidCoordinate {
ensure!(
m.x_coord >= 0,
InvalidCoordinate {
coord: "x".to_string(), coord: "x".to_string(),
range: "0-128".to_string(), range: "0-128".to_string(),
value: m.x_coord, value: msg.x_coord,
} })
); } else if msg.y_coord < 0 || msg.y_coord > 147 {
Err(OledError::InvalidCoordinate {
ensure!(
m.x_coord < 129,
InvalidCoordinate {
coord: "x".to_string(),
range: "0-128".to_string(),
value: m.x_coord,
}
);
ensure!(
m.y_coord >= 0,
InvalidCoordinate {
coord: "y".to_string(), coord: "y".to_string(),
range: "0-47".to_string(), range: "0-47".to_string(),
value: m.y_coord, value: msg.y_coord,
} })
); } else {
Ok(())
ensure!( }
m.y_coord < 148,
InvalidCoordinate {
coord: "y".to_string(),
range: "0-47".to_string(),
value: m.y_coord,
}
);
Ok(())
} }
pub fn run() -> Result<(), BoxError> { pub fn run() -> Result<(), OledError> {
info!("Starting up."); info!("Starting up.");
debug!("Creating interface for I2C device."); debug!("Creating interface for I2C device.");
let i2c = I2cdev::new("/dev/i2c-1").context(I2CError)?; let i2c = I2cdev::new("/dev/i2c-1").map_err(|source| OledError::I2CError { source })?;
let mut disp: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into();
info!("Initializing the display."); info!("Initializing the display.");
disp.init().unwrap_or_else(|_| { display.init().unwrap_or_else(|_| {
error!("Problem initializing the OLED display."); error!("Problem initializing the OLED display.");
process::exit(1); process::exit(1);
}); });
debug!("Flushing the display."); debug!("Flushing the display.");
disp.flush().unwrap_or_else(|_| { display.flush().unwrap_or_else(|_| {
error!("Problem flushing the OLED display."); error!("Problem flushing the OLED display.");
process::exit(1); process::exit(1);
}); });
let oled = Arc::new(Mutex::new(disp)); let oled = Arc::new(Mutex::new(display));
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
info!("Creating JSON-RPC I/O handler."); info!("Creating JSON-RPC I/O handler.");
let mut io = IoHandler::default(); let mut io = IoHandler::default();
io.add_method("clear", move |_| { io.add_sync_method("clear", move |_| {
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Clearing the display."); info!("Clearing the display.");
oled.clear(); oled.clear();
@ -134,21 +109,20 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("draw", move |params: Params| { io.add_sync_method("draw", move |params: Params| {
let g: Result<Graphic, Error> = params.parse(); let graphic: Graphic = params.parse()?;
let g: Graphic = g?;
// TODO: add simple byte validation function // TODO: add simple byte validation function
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Drawing image to the display."); info!("Drawing image to the display.");
let im = let image = Image1BPP::new(&graphic.bytes, graphic.width, graphic.height)
Image1BPP::new(&g.bytes, g.width, g.height).translate(Coord::new(g.x_coord, g.y_coord)); .translate(Coord::new(graphic.x_coord, graphic.y_coord));
oled.draw(im.into_iter()); oled.draw(image.into_iter());
Ok(Value::String("success".into())) Ok(Value::String("success".into()))
}); });
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("flush", move |_| { io.add_sync_method("flush", move |_| {
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Flushing the display."); info!("Flushing the display.");
oled.flush().unwrap_or_else(|_| { oled.flush().unwrap_or_else(|_| {
@ -160,9 +134,9 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("ping", |_| Ok(Value::String("success".to_string()))); io.add_sync_method("ping", |_| Ok(Value::String("success".to_string())));
io.add_method("power", move |params: Params| { io.add_sync_method("power", move |params: Params| {
let o: Result<On, Error> = params.parse(); let o: Result<On, Error> = params.parse();
let o: On = o?; let o: On = o?;
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
@ -180,37 +154,36 @@ pub fn run() -> Result<(), BoxError> {
let oled_clone = Arc::clone(&oled); let oled_clone = Arc::clone(&oled);
io.add_method("write", move |params: Params| { io.add_sync_method("write", move |params: Params| {
info!("Received a 'write' request."); info!("Received a 'write' request.");
let m: Result<Msg, Error> = params.parse(); let msg = params.parse()?;
let m: Msg = m?; validate(&msg)?;
validate(&m)?;
let mut oled = oled_clone.lock().unwrap(); let mut oled = oled_clone.lock().unwrap();
info!("Writing to the display."); info!("Writing to the display.");
if m.font_size == "6x8" { if msg.font_size == "6x8" {
oled.draw( oled.draw(
Font6x8::render_str(&m.string) Font6x8::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "6x12" { } else if msg.font_size == "6x12" {
oled.draw( oled.draw(
Font6x12::render_str(&m.string) Font6x12::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "8x16" { } else if msg.font_size == "8x16" {
oled.draw( oled.draw(
Font8x16::render_str(&m.string) Font8x16::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} else if m.font_size == "12x16" { } else if msg.font_size == "12x16" {
oled.draw( oled.draw(
Font12x16::render_str(&m.string) Font12x16::render_str(&msg.string)
.translate(Coord::new(m.x_coord, m.y_coord)) .translate(Coord::new(msg.x_coord, msg.y_coord))
.into_iter(), .into_iter(),
); );
} }
@ -255,7 +228,7 @@ mod tests {
fn rpc_success() { fn rpc_success() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_success_response", |_| { io.add_sync_method("rpc_success_response", |_| {
Ok(Value::String("success".into())) Ok(Value::String("success".into()))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
@ -269,7 +242,7 @@ mod tests {
fn rpc_internal_error() { fn rpc_internal_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_internal_error", |_| Err(Error::internal_error())); io.add_sync_method("rpc_internal_error", |_| Err(Error::internal_error()));
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };
@ -287,7 +260,7 @@ mod tests {
fn rpc_i2c_io_error() { fn rpc_i2c_io_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_i2c_io_error", |_| { io.add_sync_method("rpc_i2c_io_error", |_| {
let io_err = IoError::new(ErrorKind::PermissionDenied, "oh no!"); let io_err = IoError::new(ErrorKind::PermissionDenied, "oh no!");
let source = LinuxI2CError::Io(io_err); let source = LinuxI2CError::Io(io_err);
Err(Error::from(OledError::I2CError { source })) Err(Error::from(OledError::I2CError { source }))
@ -310,7 +283,7 @@ mod tests {
fn rpc_i2c_nix_error() { fn rpc_i2c_nix_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_i2c_nix_error", |_| { io.add_sync_method("rpc_i2c_nix_error", |_| {
let nix_err = NixError::InvalidPath; let nix_err = NixError::InvalidPath;
let source = LinuxI2CError::Nix(nix_err); let source = LinuxI2CError::Nix(nix_err);
Err(Error::from(OledError::I2CError { source })) Err(Error::from(OledError::I2CError { source }))
@ -326,14 +299,14 @@ mod tests {
}"# }"#
); );
} }
*/ */
// test to ensure correct InvalidCoordinate error response // test to ensure correct InvalidCoordinate error response
#[test] #[test]
fn rpc_invalid_coord() { fn rpc_invalid_coord() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_coord", |_| { io.add_sync_method("rpc_invalid_coord", |_| {
Err(Error::from(OledError::InvalidCoordinate { Err(Error::from(OledError::InvalidCoordinate {
coord: "x".to_string(), coord: "x".to_string(),
range: "0-128".to_string(), range: "0-128".to_string(),
@ -357,7 +330,7 @@ mod tests {
fn rpc_invalid_fontsize() { fn rpc_invalid_fontsize() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_fontsize", |_| { io.add_sync_method("rpc_invalid_fontsize", |_| {
Err(Error::from(OledError::InvalidFontSize { Err(Error::from(OledError::InvalidFontSize {
font: "24x32".to_string(), font: "24x32".to_string(),
})) }))
@ -379,7 +352,7 @@ mod tests {
fn rpc_invalid_string() { fn rpc_invalid_string() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_string", |_| { io.add_sync_method("rpc_invalid_string", |_| {
Err(Error::from(OledError::InvalidString { len: 22 })) Err(Error::from(OledError::InvalidString { len: 22 }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
@ -399,15 +372,15 @@ mod tests {
fn rpc_invalid_params() { fn rpc_invalid_params() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_invalid_params", |_| { io.add_sync_method("rpc_invalid_params", |_| {
let e = Error { let source = Error {
code: ErrorCode::InvalidParams, code: ErrorCode::InvalidParams,
message: String::from("invalid params"), message: String::from("invalid params"),
data: Some(Value::String( data: Some(Value::String(
"Invalid params: invalid type: null, expected struct Msg.".into(), "Invalid params: invalid type: null, expected struct Msg.".into(),
)), )),
}; };
Err(Error::from(OledError::MissingParameter { e })) Err(Error::from(OledError::MissingParameter { source }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };
@ -427,13 +400,13 @@ mod tests {
fn rpc_parse_error() { fn rpc_parse_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_parse_error", |_| { io.add_sync_method("rpc_parse_error", |_| {
let e = Error { let source = Error {
code: ErrorCode::ParseError, code: ErrorCode::ParseError,
message: String::from("Parse error"), message: String::from("Parse error"),
data: None, data: None,
}; };
Err(Error::from(OledError::ParseError { e })) Err(Error::from(OledError::ParseError { source }))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };

View File

@ -1,40 +1,31 @@
[package] [package]
name = "peach-stats" name = "peach-stats"
version = "0.1.3" version = "0.2.0"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <glyph@mycelial.technology>"]
edition = "2018" edition = "2018"
description = "Query system statistics using JSON-RPC over HTTP. Provides a JSON-RPC wrapper around the probes and systemstat crates." description = "Query system statistics. Provides a wrapper around the probes and systemstat crates."
keywords = ["peachcloud", "system stats", "system statistics", "disk", "memory"]
homepage = "https://opencollective.com/peachcloud" homepage = "https://opencollective.com/peachcloud"
repository = "https://github.com/peachcloud/peach-stats" repository = "https://git.coopcloud.tech/PeachCloud/peach-workspace/src/branch/main/peach-stats"
readme = "README.md" readme = "README.md"
license = "AGPL-3.0-only" license = "LGPL-3.0-only"
publish = false publish = false
[package.metadata.deb]
depends = "$auto"
extended-description = """\
peach-stats is a system statistics microservice module for PeachCloud. \
Query system statistics using JSON-RPC over HTTP. Provides a JSON-RPC \
wrapper around the probes and systemstat crates."""
maintainer-scripts="debian"
systemd-units = { unit-name = "peach-stats" }
assets = [
["target/release/peach-stats", "usr/bin/", "755"],
["README.md", "usr/share/doc/peach-stats/README", "644"],
]
[badges] [badges]
travis-ci = { repository = "peachcloud/peach-stats", branch = "master" }
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
env_logger = "0.9"
jsonrpc-core = "18"
jsonrpc-http-server = "18"
log = "0.4" log = "0.4"
miniserde = "0.1.15" miniserde = { version = "0.1.15", optional = true }
probes = "0.4.1" probes = "0.4.1"
serde = { version = "1.0.130", features = ["derive"], optional = true }
systemstat = "0.1.10" systemstat = "0.1.10"
[dev-dependencies] [features]
jsonrpc-test = "18" default = []
# Provide `Serialize` and `Deserialize` traits for library structs using `miniserde`
miniserde_support = ["miniserde"]
# Provide `Serialize` and `Deserialize` traits for library structs using `serde`
serde_support = ["serde"]

View File

@ -1,109 +1,47 @@
# peach-stats # peach-stats
[![Build Status](https://travis-ci.com/peachcloud/peach-stats.svg?branch=master)](https://travis-ci.com/peachcloud/peach-stats) ![Generic badge](https://img.shields.io/badge/version-0.1.3-<COLOR>.svg) ![Generic badge](https://img.shields.io/badge/version-0.2.0-<COLOR>.svg)
System statistics microservice module for PeachCloud. Provides a JSON-RPC wrapper around the [probes](https://crates.io/crates/probes) and [systemstat](https://crates.io/crates/systemstat) crates. System statistics library for PeachCloud. Provides a wrapper around the [probes](https://crates.io/crates/probes) and [systemstat](https://crates.io/crates/systemstat) crates.
### JSON-RPC API Currently offers the following statistics and associated data structures:
| Method | Description | Returns | - CPU: `user`, `system`, `nice`, `idle` (as values or percentages)
| --- | --- | --- | - Disk usage: `filesystem`, `one_k_blocks`, `one_k_blocks_used`,
| `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` | `one_k_blocks_free`, `used_percentage`, `mountpoint`
| `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` | - Load average: `one`, `five`, `fifteen`
| `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` | - Memory: `total`, `free`, `used`
| `load_average` | Load average statistics | `one`, `five`, `fifteen` | - Uptime: `seconds`
| `mem_stats` | Memory statistics | `total`, `free`, `used` |
| `ping` | Microservice status | `success` if running |
| `uptime` | System uptime | `secs` |
### Environment ## Example Usage
The JSON-RPC HTTP server address and port can be configured with the `PEACH_STATS_SERVER` environment variable: ```rust
use peach_stats::{stats, StatsError};
`export PEACH_STATS_SERVER=127.0.0.1:5000` fn main() -> Result<(), StatsError> {
let cpu = stats::cpu_stats()?;
let cpu_percentages = stats::cpu_stats_percent()?;
let disks = stats::disk_usage()?;
let load = stats::load_average()?;
let mem = stats::mem_stats()?;
let uptime = stats::uptime()?;
When not set, the value defaults to `127.0.0.1:5113`. // do things with the retrieved values...
Logging is made available with `env_logger`: Ok(())
}
```
`export RUST_LOG=info` ## Feature Flags
Other logging levels include `debug`, `warn` and `error`. Feature flags are used to offer `Serialize` and `Deserialize` implementations for all `struct` data types provided by this library. These traits are not provided by default. A choice of `miniserde` and `serde` is provided.
### Setup Define the desired feature in the `Cargo.toml` manifest of your project:
Clone this repo: ```toml
peach-stats = { version = "0.1.0", features = ["miniserde_support"] }
```
`git clone https://github.com/peachcloud/peach-stats.git` ## License
Move into the repo and compile a release build:
`cd peach-stats`
`cargo build --release`
Run the binary:
`./target/release/peach-stats`
### Debian Packaging
A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-stats` to be easily bundled as a Debian package (`.deb`). The `cargo-deb` [crate](https://crates.io/crates/cargo-deb) can be used to achieve this.
Install `cargo-deb`:
`cargo install cargo-deb`
Move into the repo:
`cd peach-stats`
Build the package:
`cargo deb`
The output will be written to `target/debian/peach-stats_0.1.0_arm64.deb` (or similar).
Build the package (aarch64):
`cargo deb --target aarch64-unknown-linux-gnu`
Install the package as follows:
`sudo dpkg -i target/debian/peach-stats_0.1.0_arm64.deb`
The service will be automatically enabled and started.
Uninstall the service:
`sudo apt-get remove peach-stats`
Remove configuration files (not removed with `apt-get remove`):
`sudo apt-get purge peach-stats`
### Example Usage
**Get CPU Statistics**
With microservice running, open a second terminal window and use `curl` to call server methods:
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "cpu_stats", "id":1 }' 127.0.0.1:5113`
Server responds with:
`{"jsonrpc":"2.0","result":"{\"user\":4661083,\"system\":1240371,\"idle\":326838290,\"nice\":0}","id":1}`
**Get System Uptime**
With microservice running, open a second terminal window and use `curl` to call server methods:
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "uptime", "id":1 }' 127.0.0.1:5113`
Server responds with:
`{"jsonrpc":"2.0","result":"{\"secs\":840968}","id":1}`
### Licensing
AGPL-3.0
LGPL-3.0.

View File

@ -1,27 +0,0 @@
[Unit]
Description=Query system statistics using JSON-RPC over HTTP.
[Service]
Type=simple
User=peach-stats
Environment="RUST_LOG=error"
ExecStart=/usr/bin/peach-stats
Restart=always
CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYS_BOOT CAP_SYS_TIME CAP_KILL CAP_WAKE_ALARM CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_LEASE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_RAWIO CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_* CAP_FOWNER CAP_IPC_OWNER CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_AUDIT_*
InaccessibleDirectories=/home
LockPersonality=yes
NoNewPrivileges=yes
PrivateDevices=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=yes
ReadOnlyDirectories=/var
RestrictAddressFamilies=~AF_INET6 AF_UNIX
SystemCallFilter=~@reboot @clock @debug @module @mount @swap @resources @privileged
[Install]
WantedBy=multi-user.target

View File

@ -1,69 +1,44 @@
use std::{error, fmt, io}; //! Custom error type for `peach-stats`.
use jsonrpc_core::{types::error::Error, ErrorCode};
use probes::ProbeError; use probes::ProbeError;
use std::{error, fmt, io::Error as IoError};
/// Custom error type encapsulating all possible errors when retrieving system
/// statistics.
#[derive(Debug)] #[derive(Debug)]
pub enum StatError { pub enum StatsError {
CpuStat { source: ProbeError }, /// Failed to retrieve CPU statistics.
DiskUsage { source: ProbeError }, CpuStat(ProbeError),
LoadAvg { source: ProbeError }, /// Failed to retrieve disk usage statistics.
MemStat { source: ProbeError }, DiskUsage(ProbeError),
Uptime { source: io::Error }, /// Failed to retrieve load average statistics.
LoadAvg(ProbeError),
/// Failed to retrieve memory usage statistics.
MemStat(ProbeError),
/// Failed to retrieve system uptime.
Uptime(IoError),
} }
impl error::Error for StatError {} impl error::Error for StatsError {}
impl fmt::Display for StatError { impl fmt::Display for StatsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
StatError::CpuStat { ref source } => { StatsError::CpuStat(ref source) => {
write!(f, "Failed to retrieve CPU statistics: {}", source) write!(f, "Failed to retrieve CPU statistics: {}", source)
} }
StatError::DiskUsage { ref source } => { StatsError::DiskUsage(ref source) => {
write!(f, "Failed to retrieve disk usage statistics: {}", source) write!(f, "Failed to retrieve disk usage statistics: {}", source)
} }
StatError::LoadAvg { ref source } => { StatsError::LoadAvg(ref source) => {
write!(f, "Failed to retrieve load average statistics: {}", source) write!(f, "Failed to retrieve load average statistics: {}", source)
} }
StatError::MemStat { ref source } => { StatsError::MemStat(ref source) => {
write!(f, "Failed to retrieve memory statistics: {}", source) write!(f, "Failed to retrieve memory statistics: {}", source)
} }
StatError::Uptime { ref source } => { StatsError::Uptime(ref source) => {
write!(f, "Failed to retrieve system uptime: {}", source) write!(f, "Failed to retrieve system uptime: {}", source)
} }
} }
} }
} }
impl From<StatError> for Error {
fn from(err: StatError) -> Self {
match &err {
StatError::CpuStat { source } => Error {
code: ErrorCode::ServerError(-32001),
message: format!("Failed to retrieve CPU statistics: {}", source),
data: None,
},
StatError::DiskUsage { source } => Error {
code: ErrorCode::ServerError(-32001),
message: format!("Failed to retrieve disk usage statistics: {}", source),
data: None,
},
StatError::LoadAvg { source } => Error {
code: ErrorCode::ServerError(-32001),
message: format!("Failed to retrieve load average statistics: {}", source),
data: None,
},
StatError::MemStat { source } => Error {
code: ErrorCode::ServerError(-32001),
message: format!("Failed to retrieve memory statistics: {}", source),
data: None,
},
StatError::Uptime { source } => Error {
code: ErrorCode::ServerError(-32001),
message: format!("Failed to retrieve system uptime: {}", source),
data: None,
},
}
}
}

View File

@ -1,103 +1,48 @@
mod error; #![warn(missing_docs)]
mod stats;
mod structs;
use std::{env, result::Result}; //! # peach-stats
//!
//! System statistics retrieval library; designed for use with the PeachCloud platform.
//!
//! Currently offers the following statistics and associated data structures:
//!
//! - CPU: `user`, `system`, `nice`, `idle` (as values or percentages)
//! - Disk usage: `filesystem`, `one_k_blocks`, `one_k_blocks_used`,
//! `one_k_blocks_free`, `used_percentage`, `mountpoint`
//! - Load average: `one`, `five`, `fifteen`
//! - Memory: `total`, `free`, `used`
//! - Uptime: `seconds`
//!
//! ## Example Usage
//!
//! ```rust
//! use peach_stats::{stats, StatsError};
//!
//! fn main() -> Result<(), StatsError> {
//! let cpu = stats::cpu_stats()?;
//! let cpu_percentages = stats::cpu_stats_percent()?;
//! let disks = stats::disk_usage()?;
//! let load = stats::load_average()?;
//! let mem = stats::mem_stats()?;
//! let uptime = stats::uptime()?;
//!
//! Ok(())
//! }
//! ```
//!
//! ## Feature Flags
//!
//! Feature flags are used to offer `Serialize` and `Deserialize` implementations
//! for all `struct` data types provided by this library. These traits are not
//! provided by default. A choice of `miniserde` and `serde` is provided.
//!
//! Define the desired feature in the `Cargo.toml` manifest of your project:
//!
//! ```toml
//! peach-stats = { version = "0.1.0", features = ["miniserde_support"] }
//! ```
use jsonrpc_core::{IoHandler, Value}; pub mod error;
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; pub mod stats;
use log::info;
use crate::error::StatError; pub use crate::error::StatsError;
pub fn run() -> Result<(), StatError> {
info!("Starting up.");
info!("Creating JSON-RPC I/O handler.");
let mut io = IoHandler::default();
io.add_method("cpu_stats", move |_| async {
info!("Fetching CPU statistics.");
let stats = stats::cpu_stats()?;
Ok(Value::String(stats))
});
io.add_method("cpu_stats_percent", move |_| async {
info!("Fetching CPU statistics as percentages.");
let stats = stats::cpu_stats_percent()?;
Ok(Value::String(stats))
});
io.add_method("disk_usage", move |_| async {
info!("Fetching disk usage statistics.");
let disks = stats::disk_usage()?;
Ok(Value::String(disks))
});
io.add_method("load_average", move |_| async {
info!("Fetching system load average statistics.");
let avg = stats::load_average()?;
Ok(Value::String(avg))
});
io.add_method("mem_stats", move |_| async {
info!("Fetching current memory statistics.");
let mem = stats::mem_stats()?;
Ok(Value::String(mem))
});
io.add_method("ping", |_| async {
Ok(Value::String("success".to_string()))
});
io.add_method("uptime", move |_| async {
info!("Fetching system uptime.");
let uptime = stats::uptime()?;
Ok(Value::String(uptime))
});
let http_server = env::var("PEACH_OLED_STATS").unwrap_or_else(|_| "127.0.0.1:5113".to_string());
info!("Starting JSON-RPC server on {}.", http_server);
let server = ServerBuilder::new(io)
.cors(DomainsValidation::AllowOnly(vec![
AccessControlAllowOrigin::Null,
]))
.start_http(
&http_server
.parse()
.expect("Invalid HTTP address and port combination"),
)
.expect("Unable to start RPC server");
info!("Listening for requests.");
server.wait();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use jsonrpc_test as test_rpc;
// test to ensure correct success response
#[test]
fn rpc_success() {
let rpc = {
let mut io = IoHandler::new();
io.add_method("rpc_success_response", |_| async {
Ok(Value::String("success".into()))
});
test_rpc::Rpc::from(io)
};
assert_eq!(rpc.request("rpc_success_response", &()), r#""success""#);
}
}

View File

@ -1,14 +0,0 @@
use std::process;
use log::error;
fn main() {
// initialize the logger
env_logger::init();
// handle errors returned from `run`
if let Err(e) = peach_stats::run() {
error!("Application error: {}", e);
process::exit(1);
}
}

View File

@ -1,14 +1,96 @@
//! System statistics retrieval functions and associated data types.
use std::result::Result; use std::result::Result;
use miniserde::json; #[cfg(feature = "miniserde_support")]
use miniserde::{Deserialize, Serialize};
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
use probes::{cpu, disk_usage, load, memory}; use probes::{cpu, disk_usage, load, memory};
use systemstat::{Platform, System}; use systemstat::{Platform, System};
use crate::error::StatError; use crate::error::StatsError;
use crate::structs::{CpuStat, CpuStatPercentages, DiskUsage, LoadAverage, MemStat};
pub fn cpu_stats() -> Result<String, StatError> { /// CPU statistics.
let cpu_stats = cpu::proc::read().map_err(|source| StatError::CpuStat { source })?; #[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct CpuStat {
/// Time spent running user space (application) code.
pub user: u64,
/// Time spent running kernel code.
pub system: u64,
/// Time spent doing nothing.
pub idle: u64,
/// Time spent running user space processes which have been niced.
pub nice: u64,
}
/// CPU statistics as percentages.
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct CpuStatPercentages {
/// Time spent running user space (application) code.
pub user: f32,
/// Time spent running kernel code.
pub system: f32,
/// Time spent doing nothing.
pub idle: f32,
/// Time spent running user space processes which have been niced.
pub nice: f32,
}
/// Disk usage statistics.
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct DiskUsage {
/// Filesystem device path.
pub filesystem: Option<String>,
/// Total amount of disk space as a number of 1,000 kilobyte blocks.
pub one_k_blocks: u64,
/// Total amount of used disk space as a number of 1,000 kilobyte blocks.
pub one_k_blocks_used: u64,
/// Total amount of free / available disk space as a number of 1,000 kilobyte blocks.
pub one_k_blocks_free: u64,
/// Total amount of used disk space as a percentage.
pub used_percentage: u32,
/// Mountpoint of the disk / partition.
pub mountpoint: String,
}
/// Load average statistics.
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct LoadAverage {
/// Average computational work performed over the past minute.
pub one: f32,
/// Average computational work performed over the past five minutes.
pub five: f32,
/// Average computational work performed over the past fifteen minutes.
pub fifteen: f32,
}
/// Memory statistics.
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct MemStat {
/// Total amount of physical memory in kilobytes.
pub total: u64,
/// Total amount of free / available physical memory in kilobytes.
pub free: u64,
/// Total amount of used physical memory in kilobytes.
pub used: u64,
}
/// Retrieve the current CPU statistics.
pub fn cpu_stats() -> Result<CpuStat, StatsError> {
let cpu_stats = cpu::proc::read().map_err(StatsError::CpuStat)?;
let s = cpu_stats.stat; let s = cpu_stats.stat;
let cpu = CpuStat { let cpu = CpuStat {
user: s.user, user: s.user,
@ -16,13 +98,13 @@ pub fn cpu_stats() -> Result<String, StatError> {
nice: s.nice, nice: s.nice,
idle: s.idle, idle: s.idle,
}; };
let json_cpu = json::to_string(&cpu);
Ok(json_cpu) Ok(cpu)
} }
pub fn cpu_stats_percent() -> Result<String, StatError> { /// Retrieve the current CPU statistics as percentages.
let cpu_stats = cpu::proc::read().map_err(|source| StatError::CpuStat { source })?; pub fn cpu_stats_percent() -> Result<CpuStatPercentages, StatsError> {
let cpu_stats = cpu::proc::read().map_err(StatsError::CpuStat)?;
let s = cpu_stats.stat.in_percentages(); let s = cpu_stats.stat.in_percentages();
let cpu = CpuStatPercentages { let cpu = CpuStatPercentages {
user: s.user, user: s.user,
@ -30,13 +112,13 @@ pub fn cpu_stats_percent() -> Result<String, StatError> {
nice: s.nice, nice: s.nice,
idle: s.idle, idle: s.idle,
}; };
let json_cpu = json::to_string(&cpu);
Ok(json_cpu) Ok(cpu)
} }
pub fn disk_usage() -> Result<String, StatError> { /// Retrieve the current disk usage statistics for each available disk / partition.
let disks = disk_usage::read().map_err(|source| StatError::DiskUsage { source })?; pub fn disk_usage() -> Result<Vec<DiskUsage>, StatsError> {
let disks = disk_usage::read().map_err(StatsError::DiskUsage)?;
let mut disk_usages = Vec::new(); let mut disk_usages = Vec::new();
for d in disks { for d in disks {
let disk = DiskUsage { let disk = DiskUsage {
@ -49,42 +131,39 @@ pub fn disk_usage() -> Result<String, StatError> {
}; };
disk_usages.push(disk); disk_usages.push(disk);
} }
let json_disks = json::to_string(&disk_usages);
Ok(json_disks) Ok(disk_usages)
} }
pub fn load_average() -> Result<String, StatError> { /// Retrieve the current load average statistics.
let l = load::read().map_err(|source| StatError::LoadAvg { source })?; pub fn load_average() -> Result<LoadAverage, StatsError> {
let l = load::read().map_err(StatsError::LoadAvg)?;
let load_avg = LoadAverage { let load_avg = LoadAverage {
one: l.one, one: l.one,
five: l.five, five: l.five,
fifteen: l.fifteen, fifteen: l.fifteen,
}; };
let json_load_avg = json::to_string(&load_avg);
Ok(json_load_avg) Ok(load_avg)
} }
pub fn mem_stats() -> Result<String, StatError> { /// Retrieve the current memory usage statistics.
let m = memory::read().map_err(|source| StatError::MemStat { source })?; pub fn mem_stats() -> Result<MemStat, StatsError> {
let m = memory::read().map_err(StatsError::MemStat)?;
let mem = MemStat { let mem = MemStat {
total: m.total(), total: m.total(),
free: m.free(), free: m.free(),
used: m.used(), used: m.used(),
}; };
let json_mem = json::to_string(&mem);
Ok(json_mem) Ok(mem)
} }
pub fn uptime() -> Result<String, StatError> { /// Retrieve the system uptime in seconds.
pub fn uptime() -> Result<u64, StatsError> {
let sys = System::new(); let sys = System::new();
let uptime = sys let uptime = sys.uptime().map_err(StatsError::Uptime)?;
.uptime()
.map_err(|source| StatError::Uptime { source })?;
let uptime_secs = uptime.as_secs(); let uptime_secs = uptime.as_secs();
let json_uptime = json::to_string(&uptime_secs);
Ok(json_uptime) Ok(uptime_secs)
} }

View File

@ -1,41 +0,0 @@
use miniserde::Serialize;
#[derive(Debug, Serialize)]
pub struct CpuStat {
pub user: u64,
pub system: u64,
pub idle: u64,
pub nice: u64,
}
#[derive(Debug, Serialize)]
pub struct CpuStatPercentages {
pub user: f32,
pub system: f32,
pub idle: f32,
pub nice: f32,
}
#[derive(Debug, Serialize)]
pub struct DiskUsage {
pub filesystem: Option<String>,
pub one_k_blocks: u64,
pub one_k_blocks_used: u64,
pub one_k_blocks_free: u64,
pub used_percentage: u32,
pub mountpoint: String,
}
#[derive(Debug, Serialize)]
pub struct LoadAverage {
pub one: f32,
pub five: f32,
pub fifteen: f32,
}
#[derive(Debug, Serialize)]
pub struct MemStat {
pub total: u64,
pub free: u64,
pub used: u64,
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-web" name = "peach-web"
version = "0.4.11" version = "0.4.12"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018" edition = "2018"
description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins." description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins."
@ -38,17 +38,16 @@ maintenance = { status = "actively-developed" }
env_logger = "0.8" env_logger = "0.8"
log = "0.4" log = "0.4"
nest = "1.0.0" nest = "1.0.0"
openssl = { version = "0.10", features = ["vendored"] }
peach-lib = { path = "../peach-lib" } peach-lib = { path = "../peach-lib" }
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
regex = "1"
rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
snafu = "0.6" snafu = "0.6"
tera = { version = "1.12.1", features = ["builtins"] } tera = { version = "1.12.1", features = ["builtins"] }
websocket = "0.26"
regex = "1"
xdg = "2.2.0" xdg = "2.2.0"
openssl = { version = "0.10", features = ["vendored"] }
[dependencies.rocket_dyn_templates] [dependencies.rocket_dyn_templates]
version = "0.1.0-rc.1" version = "0.1.0-rc.1"

View File

@ -1,6 +1,6 @@
# peach-web # peach-web
[![Build Status](https://travis-ci.com/peachcloud/peach-web.svg?branch=master)](https://travis-ci.com/peachcloud/peach-web) ![Generic badge](https://img.shields.io/badge/version-0.4.6-<COLOR>.svg) [![Build Status](https://travis-ci.com/peachcloud/peach-web.svg?branch=master)](https://travis-ci.com/peachcloud/peach-web) ![Generic badge](https://img.shields.io/badge/version-0.4.12-<COLOR>.svg)
## Web Interface for PeachCloud ## Web Interface for PeachCloud
@ -39,12 +39,22 @@ _Note: Networking functionality requires peach-network microservice to be runnin
### Environment ### Environment
**Deployment Mode**
The web application deployment mode is configured with the `ROCKET_ENV` environment variable: The web application deployment mode is configured with the `ROCKET_ENV` environment variable:
`export ROCKET_ENV=stage` `export ROCKET_ENV=stage`
Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information. Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information.
**Authentication**
Authentication is disabled in `development` mode and enabled by default when running the application in `production` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`:
`export ROCKET_DISABLE_AUTH=true`
**Logging**
Logging is made available with `env_logger`: Logging is made available with `env_logger`:
`export RUST_LOG=info` `export RUST_LOG=info`

View File

@ -1,5 +1,7 @@
[development] [development]
template_dir = "templates/" template_dir = "templates/"
disable_auth = true
[production] [production]
template_dir = "templates/" template_dir = "templates/"
disable_auth = false

View File

@ -1,10 +1,13 @@
use log::info; use log::info;
use rocket::form::{Form, FromForm}; use rocket::form::{Form, FromForm};
use rocket::request::FlashMessage; use rocket::http::{Cookie, CookieJar, Status};
use rocket::request::{self, FlashMessage, FromRequest, Request};
use rocket::response::{Flash, Redirect}; use rocket::response::{Flash, Redirect};
use rocket::serde::json::Json; use rocket::serde::{
use rocket::serde::{Deserialize, Serialize}; json::{Json, Value},
use rocket::{get, post}; Deserialize, Serialize,
};
use rocket::{get, post, Config};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use peach_lib::error::PeachError; use peach_lib::error::PeachError;
@ -12,9 +15,6 @@ use peach_lib::password_utils;
use crate::error::PeachWebError; use crate::error::PeachWebError;
use crate::utils::{build_json_response, TemplateOrRedirect}; use crate::utils::{build_json_response, TemplateOrRedirect};
use rocket::http::{Cookie, CookieJar, Status};
use rocket::request::{self, FromRequest, Request};
use rocket::serde::json::Value;
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES // HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
@ -42,14 +42,27 @@ impl<'r> FromRequest<'r> for Authenticated {
type Error = LoginError; type Error = LoginError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let authenticated = req // check for `disable_auth` config value; set to `false` if unset
.cookies() // can be set via the `ROCKET_DISABLE_AUTH` environment variable
.get_private(AUTH_COOKIE_KEY) // - env var, if set, takes precedence over value defined in `Rocket.toml`
.and_then(|cookie| cookie.value().parse().ok()) let authentication_is_disabled: bool = match Config::figment().find_value("disable_auth") {
.map(|_value: String| Authenticated {}); // deserialize the boolean value; set to `false` if an error is encountered
match authenticated { Ok(value) => value.deserialize().unwrap_or(false),
Some(auth) => request::Outcome::Success(auth), Err(_) => false,
None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)), };
if authentication_is_disabled {
let auth = Authenticated {};
request::Outcome::Success(auth)
} else {
let authenticated = req
.cookies()
.get_private(AUTH_COOKIE_KEY)
.and_then(|cookie| cookie.value().parse().ok())
.map(|_value: String| Authenticated {});
match authenticated {
Some(auth) => request::Outcome::Success(auth),
None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)),
}
} }
} }
} }

View File

@ -52,7 +52,7 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> {
info!("Failed to register dyndns domain: {:?}", err); info!("Failed to register dyndns domain: {:?}", err);
// json response for failed update // json response for failed update
let msg: String = match err { let msg: String = match err {
PeachError::JsonRpcClientCore { source } => { PeachError::JsonRpcClientCore(source) => {
match source { match source {
Error(ErrorKind::JsonRpcError(err), _state) => match err.code { Error(ErrorKind::JsonRpcError(err), _state) => match err.code {
ErrorCode::ServerError(-32030) => { ErrorCode::ServerError(-32030) => {

View File

@ -4,17 +4,29 @@ use std::io::Read;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
use rocket::serde::json::{json, Value}; use rocket::serde::json::{json, Value};
use rocket::{Build, Config, Rocket};
use crate::utils::build_json_response; use crate::utils::build_json_response;
use super::init_rocket; use super::init_rocket;
// define authentication mode
const DISABLE_AUTH: bool = true;
/// Wrapper around `init_rocket()` to simplify the process of invoking the application with the desired authentication status. This is particularly useful for testing purposes.
fn init_test_rocket(disable_auth: bool) -> Rocket<Build> {
// set authentication based on provided `disable_auth` value
Config::figment().merge(("disable_auth", disable_auth));
init_rocket()
}
// 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)
where where
T: Into<Option<&'static str>>, T: Into<Option<&'static str>>,
{ {
let client = Client::tracked(init_rocket()).unwrap(); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).unwrap();
let response = client.get(path).dispatch(); let response = client.get(path).dispatch();
assert_eq!(response.status(), status); assert_eq!(response.status(), status);
@ -39,7 +51,7 @@ fn read_file_content(path: &str) -> Vec<u8> {
#[test] #[test]
fn index_html() { fn index_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -54,7 +66,7 @@ fn index_html() {
#[test] #[test]
fn help_html() { fn help_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/help").dispatch(); let response = client.get("/help").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -64,7 +76,7 @@ fn help_html() {
#[test] #[test]
fn login_html() { fn login_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/login").dispatch(); let response = client.get("/login").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -74,7 +86,7 @@ fn login_html() {
#[test] #[test]
fn logout_html() { fn logout_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/logout").dispatch(); let response = client.get("/logout").dispatch();
// check for 303 status (redirect to "/login") // check for 303 status (redirect to "/login")
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -83,7 +95,7 @@ fn logout_html() {
#[test] #[test]
fn power_html() { fn power_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/power").dispatch(); let response = client.get("/power").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -97,7 +109,7 @@ NOTE: these tests are comment-out for the moment, due to the fact that they invo
#[test] #[test]
fn reboot() { fn reboot() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/power/reboot").dispatch(); let response = client.get("/power/reboot").dispatch();
// check for redirect // check for redirect
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -105,7 +117,7 @@ fn reboot() {
#[test] #[test]
fn shutdown() { fn shutdown() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/power/shutdown").dispatch(); let response = client.get("/power/shutdown").dispatch();
// check for redirect // check for redirect
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -116,7 +128,7 @@ fn shutdown() {
#[test] #[test]
fn block() { fn block() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/block") .post("/scuttlebutt/block")
.header(ContentType::Form) .header(ContentType::Form)
@ -127,7 +139,7 @@ fn block() {
#[test] #[test]
fn blocks_html() { fn blocks_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/blocks").dispatch(); let response = client.get("/scuttlebutt/blocks").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -137,7 +149,7 @@ fn blocks_html() {
#[test] #[test]
fn follow() { fn follow() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/follow") .post("/scuttlebutt/follow")
.header(ContentType::Form) .header(ContentType::Form)
@ -149,7 +161,7 @@ fn follow() {
#[test] #[test]
fn follows_html() { fn follows_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/follows").dispatch(); let response = client.get("/scuttlebutt/follows").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -159,7 +171,7 @@ fn follows_html() {
#[test] #[test]
fn followers_html() { fn followers_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/followers").dispatch(); let response = client.get("/scuttlebutt/followers").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -169,7 +181,7 @@ fn followers_html() {
#[test] #[test]
fn friends_html() { fn friends_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/friends").dispatch(); let response = client.get("/scuttlebutt/friends").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -179,7 +191,7 @@ fn friends_html() {
#[test] #[test]
fn peers_html() { fn peers_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/peers").dispatch(); let response = client.get("/scuttlebutt/peers").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -189,7 +201,7 @@ fn peers_html() {
#[test] #[test]
fn private_html() { fn private_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/private").dispatch(); let response = client.get("/scuttlebutt/private").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -199,7 +211,7 @@ fn private_html() {
#[test] #[test]
fn profile_html() { fn profile_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/scuttlebutt/profile").dispatch(); let response = client.get("/scuttlebutt/profile").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -209,7 +221,7 @@ fn profile_html() {
#[test] #[test]
fn publish_post() { fn publish_post() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/publish") .post("/scuttlebutt/publish")
.header(ContentType::Form) .header(ContentType::Form)
@ -220,7 +232,7 @@ fn publish_post() {
#[test] #[test]
fn unfollow() { fn unfollow() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/scuttlebutt/unfollow") .post("/scuttlebutt/unfollow")
.header(ContentType::Form) .header(ContentType::Form)
@ -233,7 +245,7 @@ fn unfollow() {
#[test] #[test]
fn admin_settings_menu_html() { fn admin_settings_menu_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin").dispatch(); let response = client.get("/settings/admin").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -245,7 +257,7 @@ fn admin_settings_menu_html() {
#[test] #[test]
fn add_admin_html() { fn add_admin_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/add").dispatch(); let response = client.get("/settings/admin/add").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -258,7 +270,7 @@ fn add_admin_html() {
#[test] #[test]
fn add_admin() { fn add_admin() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/admin/add") .post("/settings/admin/add")
.header(ContentType::Form) .header(ContentType::Form)
@ -270,21 +282,21 @@ fn add_admin() {
#[test] #[test]
fn change_password_html() { fn change_password_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/change_password").dispatch(); let response = client.get("/settings/admin/change_password").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.into_string().unwrap(); let body = response.into_string().unwrap();
assert!(body.contains("Change Password")); assert!(body.contains("Change Password"));
assert!(body.contains("Old Password")); assert!(body.contains("Current password"));
assert!(body.contains("Enter New Password")); assert!(body.contains("New password"));
assert!(body.contains("Re-Enter New Password")); assert!(body.contains("New password duplicate"));
assert!(body.contains("Save")); assert!(body.contains("Save"));
} }
#[test] #[test]
fn configure_admin_html() { fn configure_admin_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/configure").dispatch(); let response = client.get("/settings/admin/configure").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -296,7 +308,7 @@ fn configure_admin_html() {
#[test] #[test]
fn forgot_password_html() { fn forgot_password_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/admin/forgot_password").dispatch(); let response = client.get("/settings/admin/forgot_password").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -308,7 +320,7 @@ fn forgot_password_html() {
#[test] #[test]
fn network_settings_menu_html() { fn network_settings_menu_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network").dispatch(); let response = client.get("/settings/network").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -318,7 +330,7 @@ fn network_settings_menu_html() {
#[test] #[test]
fn deploy_ap() { fn deploy_ap() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/ap/activate").dispatch(); let response = client.get("/settings/network/ap/activate").dispatch();
// check for 303 status (redirect) // check for 303 status (redirect)
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -327,7 +339,7 @@ fn deploy_ap() {
#[test] #[test]
fn dns_settings_html() { fn dns_settings_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/dns").dispatch(); let response = client.get("/settings/network/dns").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -341,7 +353,7 @@ fn dns_settings_html() {
#[test] #[test]
fn list_aps_html() { fn list_aps_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi").dispatch(); let response = client.get("/settings/network/wifi").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -353,7 +365,7 @@ fn list_aps_html() {
// TODO: needs further testing once template has been refactored // TODO: needs further testing once template has been refactored
#[test] #[test]
fn ap_details_html() { fn ap_details_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi?ssid=Home").dispatch(); let response = client.get("/settings/network/wifi?ssid=Home").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -363,7 +375,7 @@ fn ap_details_html() {
#[test] #[test]
fn deploy_client() { fn deploy_client() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi/activate").dispatch(); let response = client.get("/settings/network/wifi/activate").dispatch();
// check for 303 status (redirect) // check for 303 status (redirect)
assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.status(), Status::SeeOther);
@ -372,7 +384,7 @@ fn deploy_client() {
#[test] #[test]
fn add_ap_html() { fn add_ap_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi/add").dispatch(); let response = client.get("/settings/network/wifi/add").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -386,7 +398,7 @@ fn add_ap_html() {
#[test] #[test]
fn add_ap_ssid_html() { fn add_ap_ssid_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/settings/network/wifi/add?ssid=Home") .get("/settings/network/wifi/add?ssid=Home")
.dispatch(); .dispatch();
@ -402,7 +414,7 @@ fn add_ap_ssid_html() {
#[test] #[test]
fn add_credentials() { fn add_credentials() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/network/wifi/add") .post("/settings/network/wifi/add")
.header(ContentType::Form) .header(ContentType::Form)
@ -414,7 +426,7 @@ fn add_credentials() {
#[test] #[test]
fn forget_wifi() { fn forget_wifi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/network/wifi/forget") .post("/settings/network/wifi/forget")
.header(ContentType::Form) .header(ContentType::Form)
@ -426,7 +438,7 @@ fn forget_wifi() {
#[test] #[test]
fn modify_password() { fn modify_password() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/settings/network/wifi/modify") .post("/settings/network/wifi/modify")
.header(ContentType::Form) .header(ContentType::Form)
@ -438,7 +450,7 @@ fn modify_password() {
#[test] #[test]
fn data_usage_html() { fn data_usage_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/network/wifi/usage").dispatch(); let response = client.get("/settings/network/wifi/usage").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -453,7 +465,7 @@ fn data_usage_html() {
#[test] #[test]
fn scuttlebutt_settings_menu_html() { fn scuttlebutt_settings_menu_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/settings/scuttlebutt").dispatch(); let response = client.get("/settings/scuttlebutt").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -472,7 +484,7 @@ fn scuttlebutt_settings_menu_html() {
#[test] #[test]
fn status_html() { fn status_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/status").dispatch(); let response = client.get("/status").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -485,7 +497,7 @@ fn status_html() {
#[test] #[test]
fn network_status_html() { fn network_status_html() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client.get("/status/network").dispatch(); let response = client.get("/status/network").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(response.content_type(), Some(ContentType::HTML));
@ -502,7 +514,7 @@ fn network_status_html() {
#[test] #[test]
fn activate_ap() { fn activate_ap() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/activate_ap") .post("/api/v1/network/activate_ap")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -513,7 +525,7 @@ fn activate_ap() {
#[test] #[test]
fn activate_client() { fn activate_client() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/activate_client") .post("/api/v1/network/activate_client")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -524,7 +536,7 @@ fn activate_client() {
#[test] #[test]
fn return_ip() { fn return_ip() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/ip") .get("/api/v1/network/ip")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -538,7 +550,7 @@ fn return_ip() {
#[test] #[test]
fn return_rssi() { fn return_rssi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/rssi") .get("/api/v1/network/rssi")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -551,7 +563,7 @@ fn return_rssi() {
#[test] #[test]
fn return_ssid() { fn return_ssid() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/ssid") .get("/api/v1/network/ssid")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -564,7 +576,7 @@ fn return_ssid() {
#[test] #[test]
fn return_state() { fn return_state() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/state") .get("/api/v1/network/state")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -579,7 +591,7 @@ fn return_state() {
#[test] #[test]
fn return_status() { fn return_status() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/status") .get("/api/v1/network/status")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -592,7 +604,7 @@ fn return_status() {
#[test] #[test]
fn scan_networks() { fn scan_networks() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/network/wifi") .get("/api/v1/network/wifi")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -605,7 +617,7 @@ fn scan_networks() {
#[test] #[test]
fn add_wifi() { fn add_wifi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/wifi") .post("/api/v1/network/wifi")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -619,7 +631,7 @@ fn add_wifi() {
#[test] #[test]
fn remove_wifi() { fn remove_wifi() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/wifi/forget") .post("/api/v1/network/wifi/forget")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -633,7 +645,7 @@ fn remove_wifi() {
#[test] #[test]
fn new_password() { fn new_password() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.post("/api/v1/network/wifi/modify") .post("/api/v1/network/wifi/modify")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -647,7 +659,7 @@ fn new_password() {
#[test] #[test]
fn ping_pong() { fn ping_pong() {
let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance");
let response = client let response = client
.get("/api/v1/ping") .get("/api/v1/ping")
.header(ContentType::JSON) .header(ContentType::JSON)
@ -709,7 +721,7 @@ fn invalid_path() {
#[test] #[test]
fn invalid_get_request() { fn invalid_get_request() {
let client = Client::tracked(init_rocket()).unwrap(); let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).unwrap();
// try to get a path that doesn't exist // try to get a path that doesn't exist
let res = client let res = client

View File

@ -220,12 +220,18 @@ body {
} }
.capsule-container { .capsule-container {
margin-left: 2rem; margin-left: 1rem;
margin-right: 2rem; margin-right: 1rem;
padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
} }
@media only screen and (min-width: 600px) {
.capsule-container {
margin-left: 0;
margin-right: 0;
}
}
/* /*
* CARDS * CARDS
*/ */
@ -235,6 +241,7 @@ body {
max-height: 90vh; max-height: 90vh;
position: relative; position: relative;
width: 100%; width: 100%;
margin-top: 1rem;
} }
@media only screen and (min-width: 600px) { @media only screen and (min-width: 600px) {
@ -248,8 +255,6 @@ body {
.card-container { .card-container {
justify-content: center; justify-content: center;
padding: 0.5rem; padding: 0.5rem;
/* padding-top: 1rem; */
/* padding-bottom: 1rem; */
} }
.form-container { .form-container {
@ -560,6 +565,7 @@ html {
font-size: var(--font-size-6); font-size: var(--font-size-6);
margin-left: 2rem; margin-left: 2rem;
margin-right: 2rem; margin-right: 2rem;
margin-top: 1rem;
} }
/* /*

View File

@ -1,16 +1,28 @@
/* /*
* behavioural layer for the `change_password.html.tera` template,
*/ behavioural layer for the `change_password.html.tera` template
- intercept button click for save (form submission of passwords)
- perform json api call
- update the dom
methods:
PEACH_AUTH.changePassword();
*/
var PEACH_AUTH = {};
// catch click of 'Save' button and make POST request // catch click of 'Save' button and make POST request
PEACH.add = function() { PEACH_AUTH.changePassword = function() {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) { document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// capture form data // capture form data
var formElement = document.querySelector("form"); var formElement = document.querySelector("form");
// create form data object from the wifiCreds form element // create form data object from the changePassword form element
var formData = new FormData(formElement); var formData = new FormData(formElement);
var object = {}; var object = {};
// assign values from form // assign values from form
@ -22,7 +34,7 @@ PEACH.add = function() {
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH.flashMsg("info", "Saving new password."); PEACH.flashMsg("info", "Saving new password.");
// send add_wifi POST request // send change_password POST request
fetch("/api/v1/admin/change_password", { fetch("/api/v1/admin/change_password", {
method: "post", method: "post",
headers: { headers: {
@ -41,5 +53,5 @@ PEACH.add = function() {
}); });
} }
var addInstance = PEACH; var changePassInstance = PEACH_AUTH;
addInstance.add(); changePassInstance.changePassword();

View File

@ -43,5 +43,4 @@ PEACH.flashMsg = function(status, msg) {
} }
} }
var addInstance = PEACH; var commonInstance = PEACH;
addInstance.add();

View File

@ -1,9 +1,9 @@
/* /*
behavioural layer for the `configure_dns.html.tera` template, behavioural layer for the `configure_dns.html.tera` template,
corresponding to the web route `/network/dns` corresponding to the web route `/settings/network/dns`
- intercept button click for add (form submission of credentials) - intercept button click for save (form submission of dns settings)
- perform json api call - perform json api call
- update the dom - update the dom
@ -12,14 +12,14 @@ corresponding to the web route `/network/dns`
var PEACH_DNS = {}; var PEACH_DNS = {};
// catch click of 'Add' button and make POST request // catch click of 'Add' button and make POST request
PEACH_DNS.add = function() { PEACH_DNS.configureDns = function() {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) { document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// capture form data // capture form data
var formElement = document.querySelector("form"); var formElement = document.querySelector("form");
// create form data object from the wifiCreds form element // create form data object from the configureDNS form element
var formData = new FormData(formElement); var formData = new FormData(formElement);
var object = {}; var object = {};
// set checkbox to false (the value is only passed to formData if it is "on") // set checkbox to false (the value is only passed to formData if it is "on")
@ -36,7 +36,7 @@ PEACH_DNS.add = function() {
console.log(object); console.log(object);
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_DNS.flashMsg("info", "Saving new DNS configurations"); PEACH.flashMsg("info", "Saving new DNS configurations");
// send add_wifi POST request // send add_wifi POST request
fetch("/api/v1/network/dns/configure", { fetch("/api/v1/network/dns/configure", {
method: "post", method: "post",
@ -50,49 +50,14 @@ PEACH_DNS.add = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_DNS.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
let statusIndicator = document.getElementById("dyndns-status-indicator"); let statusIndicator = document.getElementById("dyndns-status-indicator");
statusIndicator.remove(); // only remove the "dyndns-status-indicator" element if it exists
if (statusIndicator != null ) statusIndicator.remove();
}) })
}, false); }, false);
}); });
} }
// display a message by appending a paragraph element var configureDnsInstance = PEACH_DNS;
PEACH_DNS.flashMsg = function(status, msg) { configureDnsInstance.configureDns();
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var addInstance = PEACH_DNS;
addInstance.add();

View File

@ -10,7 +10,6 @@ corresponding to the web route `/network/wifi/add`
methods: methods:
PEACH_NETWORK.add(); PEACH_NETWORK.add();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -34,7 +33,7 @@ PEACH_NETWORK.add = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Adding WiFi credentials..."); PEACH.flashMsg("info", "Adding WiFi credentials...");
// send add_wifi POST request // send add_wifi POST request
fetch("/api/v1/network/wifi", { fetch("/api/v1/network/wifi", {
method: "post", method: "post",
@ -48,46 +47,11 @@ PEACH_NETWORK.add = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}); });
} }
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var addInstance = PEACH_NETWORK; var addInstance = PEACH_NETWORK;
addInstance.add(); addInstance.add();

View File

@ -1,7 +1,7 @@
/* /*
behavioural layer for the `network_card.html.tera` template, behavioural layer for the `network_card.html.tera` template,
corresponding to the web route `/network` corresponding to the web route `/settings/network`
- intercept form submissions - intercept form submissions
- perform json api calls - perform json api calls
@ -11,10 +11,8 @@ methods:
PEACH_NETWORK.activateAp(); PEACH_NETWORK.activateAp();
PEACH_NETWORK.activateClient(); PEACH_NETWORK.activateClient();
PEACH_NETWORK.apOnline(); PEACH_NETWORK.apMode();
PEACH_NETWORK.clientOffline(); PEACH_NETWORK.clientMode();
PEACH_NETWORK.clientOnline();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -29,7 +27,7 @@ PEACH_NETWORK.activateAp = function() {
// prevent form submission (default behavior) // prevent form submission (default behavior)
e.preventDefault(); e.preventDefault();
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Deploying access point..."); PEACH.flashMsg("info", "Deploying access point...");
// send activate_ap POST request // send activate_ap POST request
fetch("/api/v1/network/activate_ap", { fetch("/api/v1/network/activate_ap", {
method: "post", method: "post",
@ -44,10 +42,10 @@ PEACH_NETWORK.activateAp = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
// if ap activation is successful, update the ui // if ap activation is successful, update the ui
if (jsonData.status === "success") { if (jsonData.status === "success") {
PEACH_NETWORK.apOnline(); PEACH_NETWORK.apMode();
} }
}) })
}, false); }, false);
@ -64,7 +62,7 @@ PEACH_NETWORK.activateClient = function() {
// prevent form submission (default behavior) // prevent form submission (default behavior)
e.preventDefault(); e.preventDefault();
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Enabling WiFi client..."); PEACH.flashMsg("info", "Enabling WiFi client...");
// send activate_ap POST request // send activate_ap POST request
fetch("/api/v1/network/activate_client", { fetch("/api/v1/network/activate_client", {
method: "post", method: "post",
@ -79,10 +77,10 @@ PEACH_NETWORK.activateClient = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
// if client activation is successful, update the ui // if client activation is successful, update the ui
if (jsonData.status === "success") { if (jsonData.status === "success") {
PEACH_NETWORK.clientOnline(); PEACH_NETWORK.clientMode();
} }
}) })
}, false); }, false);
@ -90,21 +88,12 @@ PEACH_NETWORK.activateClient = function() {
}); });
} }
// update ui for access point mode (status: online) // replace 'Deploy Access Point' button with 'Enable WiFi' button
PEACH_NETWORK.apOnline = function() { PEACH_NETWORK.apMode = function() {
console.log('Activating AP Mode');
// update network mode and status (icon & label)
let i = document.getElementById("netModeIcon");
i.className = "center icon icon-active";
i.src = "icons/router.svg";
let l = document.getElementById("netModeLabel");
l.textContent = "ONLINE";
// create Enable WiFi button and add it to button div // create Enable WiFi button and add it to button div
var wifiButton = document.createElement("A"); var wifiButton = document.createElement("A");
wifiButton.className = "button center"; wifiButton.className = "button center";
wifiButton.href = "/network/wifi/activate"; wifiButton.href = "/settings/network/wifi/activate";
wifiButton.id = "connectWifi"; wifiButton.id = "connectWifi";
var label = "Enable WiFi"; var label = "Enable WiFi";
var buttonText = document.createTextNode(label); var buttonText = document.createTextNode(label);
@ -114,88 +103,31 @@ PEACH_NETWORK.apOnline = function() {
let buttons = document.getElementById("buttons"); let buttons = document.getElementById("buttons");
buttons.appendChild(wifiButton); buttons.appendChild(wifiButton);
// remove the old 'Activate Access Point' button // remove the old 'Deploy Access Point' button
let apButton = document.getElementById("deployAccessPoint"); let apButton = document.getElementById("deployAccessPoint");
apButton.style = "display: none;"; apButton.remove();
} }
// update ui for wifi client mode (status: online) // replace 'Enable WiFi' button with 'Deploy Access Point' button
PEACH_NETWORK.clientOnline = function() { PEACH_NETWORK.clientMode = function() {
console.log('Activating Client Mode'); // create Deploy Access Point button and add it to button div
var apButton = document.createElement("A");
apButton.className = "button center";
apButton.href = "/settings/network/ap/activate";
apButton.id = "deployAccessPoint";
var label = "Deploy Access Point";
var buttonText = document.createTextNode(label);
apButton.appendChild(buttonText);
// update network mode and status (icon & label) // append the new button to the buttons div
let i = document.getElementById("netModeIcon"); let buttons = document.getElementById("buttons");
i.className = "center icon icon-active"; buttons.appendChild(apButton);
i.src = "icons/wifi.svg";
let l = document.getElementById("netModeLabel");
l.textContent = "ONLINE";
// TODO: think about updates for buttons (transition from ap mode) // remove the old 'Enable Wifi' button
} let wifiButton = document.getElementById("connectWifi");
wifiButton.remove();
// update ui for wifi client mode (status: offline)
PEACH_NETWORK.clientOffline = function() {
console.log('Activating Client Mode');
// update network mode and status (icon & label)
let i = document.getElementById("netModeIcon");
i.className = "center icon icon-inactive";
i.src = "icons/wifi.svg";
let l = document.getElementById("netModeLabel");
l.textContent = "OFFLINE";
// TODO: think about updates for buttons (transition from ap mode)
}
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div above the three icon grid div
var gridDiv = document.getElementById("gridDiv");
gridDiv.parentNode.insertBefore(flashDiv, gridDiv);
}
} }
var networkInstance = PEACH_NETWORK; var networkInstance = PEACH_NETWORK;
networkInstance.activateAp(); networkInstance.activateAp();
networkInstance.activateClient(); networkInstance.activateClient();
/*
async function exampleFetch() {
const response = await fetch('/api/v1/network/state');
const myJson = await response.json();
//const jsonData = JSON.parse(myJson);
console.log(myJson.data.wlan0);
//var state = document.createElement("P");
//state.innerText = ""jsonData.wlan0;
//document.body.appendChild(state);
}
exampleFetch()
*/

View File

@ -1,7 +1,7 @@
/* /*
behavioural layer for the `network_detail.html.tera` template, behavioural layer for the `network_detail.html.tera` template,
corresponding to the web route `/network/wifi?<ssid>` corresponding to the web route `/settings/network/wifi?<ssid>`
- intercept button clicks for connect, disconnect and forget - intercept button clicks for connect, disconnect and forget
- perform json api call - perform json api call
@ -12,7 +12,6 @@ methods:
PEACH_NETWORK.connect(); PEACH_NETWORK.connect();
PEACH_NETWORK.disconnect(); PEACH_NETWORK.disconnect();
PEACH_NETWORK.forget(); PEACH_NETWORK.forget();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -33,7 +32,7 @@ PEACH_NETWORK.connect = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(ssidData); var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Connecting to access point..."); PEACH.flashMsg("info", "Connecting to access point...");
// send add_wifi POST request // send add_wifi POST request
fetch("/api/v1/network/wifi/connect", { fetch("/api/v1/network/wifi/connect", {
method: "post", method: "post",
@ -47,7 +46,7 @@ PEACH_NETWORK.connect = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}; };
@ -69,7 +68,7 @@ PEACH_NETWORK.disconnect = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(ssidData); var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Disconnecting from access point..."); PEACH.flashMsg("info", "Disconnecting from access point...");
// send disconnect_wifi POST request // send disconnect_wifi POST request
fetch("/api/v1/network/wifi/disconnect", { fetch("/api/v1/network/wifi/disconnect", {
method: "post", method: "post",
@ -83,7 +82,7 @@ PEACH_NETWORK.disconnect = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}; };
@ -105,7 +104,7 @@ PEACH_NETWORK.forget = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(ssidData); var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Removing credentials for access point..."); PEACH.flashMsg("info", "Removing credentials for access point...");
// send forget_ap POST request // send forget_ap POST request
fetch("/api/v1/network/wifi/forget", { fetch("/api/v1/network/wifi/forget", {
method: "post", method: "post",
@ -119,48 +118,13 @@ PEACH_NETWORK.forget = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}; };
}); });
} }
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var detailInstance = PEACH_NETWORK; var detailInstance = PEACH_NETWORK;
detailInstance.connect(); detailInstance.connect();
detailInstance.disconnect(); detailInstance.disconnect();

View File

@ -9,7 +9,6 @@ behavioural layer for the `network_modify.html.tera` template
methods: methods:
PEACH_NETWORK.modify(); PEACH_NETWORK.modify();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -33,7 +32,7 @@ PEACH_NETWORK.modify = function() {
// perform json serialization // perform json serialization
var jsonData = JSON.stringify(object); var jsonData = JSON.stringify(object);
// write in-progress status message to ui // write in-progress status message to ui
PEACH_NETWORK.flashMsg("info", "Updating WiFi password..."); PEACH.flashMsg("info", "Updating WiFi password...");
// send new_password POST request // send new_password POST request
fetch("/api/v1/network/wifi/modify", { fetch("/api/v1/network/wifi/modify", {
method: "post", method: "post",
@ -47,46 +46,11 @@ PEACH_NETWORK.modify = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}); });
} }
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var modifyInstance = PEACH_NETWORK; var modifyInstance = PEACH_NETWORK;
modifyInstance.modify(); modifyInstance.modify();

View File

@ -1,7 +1,7 @@
/* /*
behavioural layer for the `network_usage.html.tera` template, behavioural layer for the `network_usage.html.tera` template,
corresponding to the web route `/network/wifi/usage` corresponding to the web route `/settings/network/wifi/usage`
- intercept form submissions - intercept form submissions
- perform json api calls - perform json api calls
@ -13,7 +13,6 @@ methods:
PEACH_NETWORK.resetUsage(); PEACH_NETWORK.resetUsage();
PEACH_NETWORK.toggleWarning(); PEACH_NETWORK.toggleWarning();
PEACH_NETWORK.toggleCutoff(); PEACH_NETWORK.toggleCutoff();
PEACH_NETWORK.flashMsg(status, msg);
*/ */
@ -51,7 +50,7 @@ PEACH_NETWORK.updateAlerts = function() {
}) })
.then( (jsonData) => { .then( (jsonData) => {
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
}); });
@ -79,7 +78,7 @@ PEACH_NETWORK.resetUsage = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_NETWORK.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
// if reset is successful, update the ui // if reset is successful, update the ui
if (jsonData.status === "success") { if (jsonData.status === "success") {
console.log(jsonData.data); console.log(jsonData.data);
@ -133,39 +132,6 @@ PEACH_NETWORK.toggleCutoff = function() {
}); });
}; };
// display a message by appending a paragraph element
PEACH_NETWORK.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var usageInstance = PEACH_NETWORK; var usageInstance = PEACH_NETWORK;
usageInstance.resetUsage(); usageInstance.resetUsage();
usageInstance.toggleWarning(); usageInstance.toggleWarning();

View File

@ -11,7 +11,6 @@ methods:
PEACH_DEVICE.reboot(); PEACH_DEVICE.reboot();
PEACH_DEVICE.shutdown(); PEACH_DEVICE.shutdown();
PEACH_DEVICE.flashMsg(status, msg);
*/ */
@ -26,7 +25,7 @@ PEACH_DEVICE.reboot = function() {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// write reboot flash message // write reboot flash message
PEACH_DEVICE.flashMsg("success", "Rebooting the device..."); PEACH.flashMsg("success", "Rebooting the device...");
// send reboot_device POST request // send reboot_device POST request
fetch("/api/v1/admin/reboot", { fetch("/api/v1/admin/reboot", {
method: "post", method: "post",
@ -41,7 +40,7 @@ PEACH_DEVICE.reboot = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_DEVICE.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
} }
@ -57,7 +56,7 @@ PEACH_DEVICE.shutdown = function() {
// prevent form submission (default behavior) // prevent form submission (default behavior)
e.preventDefault(); e.preventDefault();
// write shutdown flash message // write shutdown flash message
PEACH_DEVICE.flashMsg("success", "Shutting down the device..."); PEACH.flashMsg("success", "Shutting down the device...");
// send shutdown_device POST request // send shutdown_device POST request
fetch("/api/v1/shutdown", { fetch("/api/v1/shutdown", {
method: "post", method: "post",
@ -72,48 +71,13 @@ PEACH_DEVICE.shutdown = function() {
.then( (jsonData) => { .then( (jsonData) => {
console.log(jsonData.msg); console.log(jsonData.msg);
// write json response message to ui // write json response message to ui
PEACH_DEVICE.flashMsg(jsonData.status, jsonData.msg); PEACH.flashMsg(jsonData.status, jsonData.msg);
}) })
}, false); }, false);
} }
}); });
} }
// display a message by appending a paragraph element
PEACH_DEVICE.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var deviceInstance = PEACH_DEVICE; var deviceInstance = PEACH_DEVICE;
deviceInstance.reboot(); deviceInstance.reboot();
deviceInstance.shutdown(); deviceInstance.shutdown();

View File

@ -2,15 +2,17 @@
* behavioural layer for the `reset_password.html.tera` template, * behavioural layer for the `reset_password.html.tera` template,
*/ */
var PEACH_AUTH = {};
// catch click of 'Save' button and make POST request // catch click of 'Save' button and make POST request
PEACH.add = function() { PEACH_AUTH.resetPassword = function() {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) { document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior) // prevent redirect on button press (default behavior)
e.preventDefault(); e.preventDefault();
// capture form data // capture form data
var formElement = document.querySelector("form"); var formElement = document.querySelector("form");
// create form data object from the wifiCreds form element // create form data object from the changePassword form element
var formData = new FormData(formElement); var formData = new FormData(formElement);
var object = {}; var object = {};
// assign values from form // assign values from form
@ -41,5 +43,5 @@ PEACH.add = function() {
}); });
} }
var addInstance = PEACH; var resetPassInstance = PEACH_AUTH;
addInstance.add(); resetPassInstance.resetPassword();

View File

@ -1,9 +1,11 @@
{%- extends "nav" -%} {%- extends "nav" -%}
{%- block card %} {%- block card %}
<div class="card center"> <div class="card center">
<div class="card-container capsule info-border"> <div class="capsule-container">
<p>No PeachCloud resource exists for this URL. Please ensure that the URL in the address bar is correct.</p> <div class="capsule info-border">
<p>Click the back arrow in the top-left or the PeachCloud logo at the bottom of your screen to return Home.</p> <p>No PeachCloud resource exists for this URL. Please ensure that the URL in the address bar is correct.</p>
</div> <p>Click the back arrow in the top-left or the PeachCloud logo at the bottom of your screen to return Home.</p>
</div>
</div> </div>
</div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -4,47 +4,22 @@
<div class="card center"> <div class="card center">
<div class="form-container"> <div class="form-container">
<form id="changePassword" action="/settings/change_password" method="post"> <form id="changePassword" action="/settings/change_password" method="post">
<div class="input-wrapper"> <!-- input for current password -->
<!-- input for old password --> <input id="currentPassword" class="center input" name="current_password" type="password" placeholder="Current password" title="Current password" autofocus>
<label id="old_password" class="label-small input-label font-near-black"> <!-- input for new password -->
<label class="label-small input-label font-gray" for="old_password" style="padding-top: 0.25rem;">Old Password</label> <input id="newPassword" class="center input" name="new_password1" type="password" placeholder="New password" title="New password">
<input id="old_password" class="form-input" style="margin-bottom: 0;" <!-- input for duplicate new password -->
name="old_password" type="password" title="old password" value=""> <input id="newPasswordDuplicate" class="center input" name="new_password2" type="password" placeholder="Re-enter new password" title="New password duplicate">
</label>
</div>
<div class="input-wrapper">
<!-- input for new password1 -->
<label id="new_password1" class="label-small input-label font-near-black">
<label class="label-small input-label font-gray" for="new_password1" style="padding-top: 0.25rem;">Enter New Password</label>
<input id="new_password1" class="form-input" style="margin-bottom: 0;"
name="new_password1" title="new_password1" type="password" value="">
</label>
</div>
<div class="input-wrapper">
<!-- input for new password2 -->
<label id="new_password2" class="label-small input-label font-near-black">
<label class="label-small input-label font-gray" for="new_password2" style="padding-top: 0.25rem;">Re-Enter New Password</label>
<input id="new_password2" class="form-input" style="margin-bottom: 0;"
name="new_password2" title="new_password2" type="password" value="">
</label>
</div>
<div id="buttonDiv"> <div id="buttonDiv">
<input id="changePasswordButton" class="button button-primary center" title="Add" type="submit" value="Save"> <input id="savePassword" class="button button-primary center" title="Add" type="submit" value="Save">
<a class="button button-secondary center" href="/settings/admin" title="Cancel">Cancel</a>
</div> </div>
<a class="button button-secondary center" href="/settings/admin" title="Cancel">Cancel</a>
</form> </form>
<!-- FLASH MESSAGE --> <!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %} {% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED --> <!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %} {% include "snippets/noscript" %}
</div> </div>
</div> </div>
<script type="text/javascript" src="/js/change_password.js"></script> <script type="text/javascript" src="/js/change_password.js"></script>
{%- endblock card -%} {%- endblock card -%}

View File

@ -1,25 +1,20 @@
{%- extends "nav" -%} {%- extends "nav" -%}
{%- block card %} {%- block card %}
<!--PUBLIC PAGE FOR SENDING A NEW TEMPORARY PASSWORD TO BE USED TO RESET YOUR PASSWORD --> <!-- PASSWORD RESET REQUEST CARD -->
<div class="card center"> <div class="card center">
<p class="text-notice" style="width: 80%; margin:auto; margin-bottom: 35px; margin-top: 20px;"> <div class="capsule capsule-container info-border">
Click the button below to send a new temporary password which can be used to change your device password. <p class="card-text">Click the button below to send a new temporary password which can be used to change your device password.
<br/><br/> </br></br>
The temporary password will be sent in an SSB private message to the admin of this device. The temporary password will be sent in an SSB private message to the admin of this device.</p>
</p> </div>
<form id="sendPasswordReset" action="/send_password_reset" method="post"> <form id="sendPasswordReset" action="/send_password_reset" method="post">
<div id="buttonDiv"> <div id="buttonDiv">
<input type="submit" class="button center button-secondary" value="Send Password Reset" title="Send Password Reset Link"/> <input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Password Reset" title="Send Password Reset Link"/>
</div> </div>
</form> </form>
<!-- FLASH MESSAGE --> <!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %} {% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED --> <!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %} {% include "snippets/noscript" %}
</div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -2,12 +2,10 @@
{%- block card %} {%- block card %}
<!-- ADMIN SETTINGS MENU --> <!-- ADMIN SETTINGS MENU -->
<div class="card center"> <div class="card center">
<div class="card-container"> <!-- BUTTONS -->
<!-- BUTTONS --> <div id="settingsButtons">
<div id="settingsButtons"> <a id="change" class="button button-primary center" href="/settings/admin/change_password" title="Change Password">Change Password</a>
<a id="change" class="button button-primary center" href="/settings/admin/change_password" title="Change Password">Change Password</a> <a id="configure" class="button button-primary center" href="/settings/admin/configure" title="Configure Admin">Configure Admin</a>
<a id="configure" class="button button-primary center" href="/settings/admin/configure" title="Configure Admin">Configure Admin</a>
</div>
</div> </div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -2,13 +2,11 @@
{%- block card %} {%- block card %}
<!-- SETTINGS MENU --> <!-- SETTINGS MENU -->
<div class="card center"> <div class="card center">
<div class="card-container"> <!-- BUTTONS -->
<!-- BUTTONS --> <div id="settingsButtons">
<div id="settingsButtons"> <a id="network" class="button button-primary center" href="/settings/network" title="Network Settings">Network</a>
<a id="network" class="button button-primary center" href="/settings/network" title="Network Settings">Network</a> <a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings">Scuttlebutt</a>
<a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings">Scuttlebutt</a> <a id="admin" class="button button-primary center" href="/settings/admin" title="Administrator Settings">Administration</a>
<a id="admin" class="button button-primary center" href="/settings/admin" title="Administrator Settings">Administration</a>
</div>
</div> </div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}

View File

@ -2,19 +2,17 @@
{%- block card %} {%- block card %}
<!-- SCUTTLEBUTT SETTINGS MENU --> <!-- SCUTTLEBUTT SETTINGS MENU -->
<div class="card center"> <div class="card center">
<div class="card-container"> <!-- BUTTONS -->
<!-- BUTTONS --> <div id="settingsButtons">
<div id="settingsButtons"> <a id="networkKey" class="button button-primary center" href="/settings/scuttlebutt/network_key" title="Set Network Key">Set Network Key</a>
<a id="networkKey" class="button button-primary center" href="/settings/scuttlebutt/network_key" title="Set Network Key">Set Network Key</a> <a id="replicationHops" class="button button-primary center" href="/settings/scuttlebutt/hops" title="Set Replication Hops">Set Replication Hops</a>
<a id="replicationHops" class="button button-primary center" href="/settings/scuttlebutt/hops" title="Set Replication Hops">Set Replication Hops</a> <a id="removeFeeds" class="button button-primary center" href="/settings/scuttlebutt/remove_feeds" title="Remove Blocked Feeds">Remove Blocked Feeds</a>
<a id="removeFeeds" class="button button-primary center" href="/settings/scuttlebutt/remove_feeds" title="Remove Blocked Feeds">Remove Blocked Feeds</a> <a id="setDirectory" class="button button-primary center" href="/settings/scuttlebutt/set_directory" title="Set Database Directory">Set Database Directory</a>
<a id="setDirectory" class="button button-primary center" href="/settings/scuttlebutt/set_directory" title="Set Database Directory">Set Database Directory</a> <a id="checkFilesystem" class="button button-primary center" href="/settings/scuttlebutt/check_fs" title="Check Filesystem">Check Filesystem</a>
<a id="checkFilesystem" class="button button-primary center" href="/settings/scuttlebutt/check_fs" title="Check Filesystem">Check Filesystem</a> <a id="repairFilesystem" class="button button-primary center" href="/settings/scuttlebutt/repair" title="Repair Filesystem">Repair Filesystem</a>
<a id="repairFilesystem" class="button button-primary center" href="/settings/scuttlebutt/repair" title="Repair Filesystem">Repair Filesystem</a> <a id="disable" class="button button-primary center" href="/settings/scuttlebutt/disable" title="Disable Sbot">Disable Sbot</a>
<a id="disable" class="button button-primary center" href="/settings/scuttlebutt/disable" title="Disable Sbot">Disable Sbot</a> <a id="enable" class="button button-primary center" href="/settings/scuttlebutt/enable" title="Enable Sbot">Enable Sbot</a>
<a id="enable" class="button button-primary center" href="/settings/scuttlebutt/enable" title="Enable Sbot">Enable Sbot</a> <a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot">Restart Sbot</a>
<a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot">Restart Sbot</a>
</div>
</div> </div>
</div> </div>
{%- endblock card -%} {%- endblock card -%}