Upgrade peach-web to use rocket 0.5 #15

Merged
notplants merged 9 commits from rocket0.5 into main 2021-11-05 11:07:03 +00:00
17 changed files with 1640 additions and 945 deletions

1693
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,7 @@ log = "0.4"
nest = "1.0.0"
peach-lib = { path = "../peach-lib" }
percent-encoding = "2.1.0"
rocket = "0.4.6"
rocket = { version = "0.5.0-rc.1", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
snafu = "0.6"
@ -50,7 +50,6 @@ regex = "1"
xdg = "2.2.0"
openssl = { version = "0.10", features = ["vendored"] }
[dependencies.rocket_contrib]
version = "0.4.10"
default-features = false
features = ["json", "tera_templates"]
[dependencies.rocket_dyn_templates]
version = "0.1.0-rc.1"
features = ["tera"]

View File

@ -1,150 +0,0 @@
//! # peach-web
//!
//! `peach-web` 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.
//!
//! ## Design
//!
//! `peach-web` is written primarily in Rust and presents a web interface for
//! interacting with the device. The stack currently consists of Rocket (Rust
//! web framework), Tera (Rust template engine inspired by Jinja2 and the Django
//! template language), HTML, CSS and JavaScript. Additional functionality is
//! provided by JSON-RPC clients for the `peach-network` and `peach-stats`
//! microservices.
//!
//! HTML is rendered server-side. Request handlers call JSON-RPC microservices
//! and serve HTML and assets. A JSON API is exposed for remote calls and
//! dynamic client-side content updates via vanilla JavaScript following
//! unobstructive design principles. A basic Websockets server is included,
//! though is not currently utilised. Each Tera template is passed a context
//! object. In the case of Rust, this object is a `struct` and must implement
//! `Serialize`. The fields of the context object are available in the context
//! of the template to be rendered.
#![feature(proc_macro_hygiene, decl_macro)]
pub mod error;
pub mod routes;
#[cfg(test)]
mod tests;
pub mod utils;
mod ws;
use std::{env, thread};
use log::{debug, error, info};
use rocket::{catchers, routes};
use rocket_contrib::templates::Template;
use crate::routes::authentication::*;
use crate::routes::device::*;
use crate::routes::helpers::*;
use crate::routes::index::*;
use crate::routes::ping::*;
use crate::routes::scuttlebutt::*;
use crate::routes::settings::admin::*;
use crate::routes::settings::dns::*;
use crate::routes::settings::network::*;
use crate::ws::*;
pub type BoxError = Box<dyn std::error::Error>;
// create rocket instance & mount web & json routes (makes testing easier)
fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount(
"/",
routes![
add_credentials, // WEB ROUTE
connect_wifi, // WEB ROUTE
disconnect_wifi, // WEB ROUTE
deploy_ap, // WEB ROUTE
deploy_client, // WEB ROUTE
device_stats, // WEB ROUTE
files, // WEB ROUTE
forget_wifi, // WEB ROUTE
help, // WEB ROUTE
index, // WEB ROUTE
login, // WEB ROUTE
logout, // WEB ROUTE
messages, // WEB ROUTE
network_home, // WEB ROUTE
network_add_ssid, // WEB ROUTE
network_add_wifi, // WEB ROUTE
network_detail, // WEB ROUTE
peers, // WEB ROUTE
profile, // WEB ROUTE
reboot_cmd, // WEB ROUTE
shutdown_cmd, // WEB ROUTE
shutdown_menu, // WEB ROUTE
wifi_list, // WEB ROUTE
wifi_password, // WEB ROUTE
wifi_set_password, // WEB ROUTE
wifi_usage, // WEB ROUTE
wifi_usage_alerts, // WEB ROUTE
wifi_usage_reset, // WEB ROUTE
configure_dns, // WEB ROUTE
configure_dns_post, // WEB ROUTE
change_password, // WEB ROUTE
reset_password, // WEB ROUTE
reset_password_post, // WEB ROUTE
send_password_reset_page, // WEB ROUTE
send_password_reset_post, // WEB ROUTE
configure_admin, // WEB ROUTE
add_admin, // WEB ROUTE
add_admin_post, // WEB ROUTE
delete_admin_post, // WEB ROUTE
activate_ap, // JSON API
activate_client, // JSON API
add_wifi, // JSON API
connect_ap, // JSON API
disconnect_ap, // JSON API
forget_ap, // JSON API
modify_password, // JSON API
ping_pong, // JSON API
test_route, // JSON API
ping_network, // JSON API
ping_oled, // JSON API
ping_stats, // JSON API
reset_data_total, // JSON API
return_ip, // JSON API
return_rssi, // JSON API
return_ssid, // JSON API
return_state, // JSON API
return_status, // JSON API
reboot_device, // JSON API
scan_networks, // JSON API
shutdown_device, // JSON API
update_wifi_alerts, // JSON API
save_dns_configuration_endpoint, // JSON API
save_password_form_endpoint, // JSON API
reset_password_form_endpoint, // JSON API
],
)
.register(catchers![not_found, internal_error])
.attach(Template::fairing())
}
// launch the rocket server
pub fn run() -> Result<(), BoxError> {
info!("Starting up.");
// spawn a separate thread for rocket to prevent blocking websockets
thread::spawn(|| {
info!("Launching Rocket server.");
rocket().launch();
});
// NOTE: websockets are not currently in use (may be in the future)
let ws_addr = env::var("PEACH_WEB_WS").unwrap_or_else(|_| "0.0.0.0:5115".to_string());
match websocket_server(ws_addr) {
Ok(_) => debug!("Websocket server terminated without error."),
Err(e) => error!("Error starting the websocket server: {}", e),
};
Ok(())
}

View File

@ -1,16 +1,145 @@
//! Initialize the logger and run the application, catching any errors.
//! # peach-web
//!
//! `peach-web` 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.
//!
//! ## Design
//!
//! `peach-web` is written primarily in Rust and presents a web interface for
//! interacting with the device. The stack currently consists of Rocket (Rust
//! web framework), Tera (Rust template engine inspired by Jinja2 and the Django
//! template language), HTML, CSS and JavaScript. Additional functionality is
//! provided by JSON-RPC clients for the `peach-network` and `peach-stats`
//! microservices.
//!
//! HTML is rendered server-side. Request handlers call JSON-RPC microservices
//! and serve HTML and assets. A JSON API is exposed for remote calls and
//! dynamic client-side content updates via vanilla JavaScript following
//! unobstructive design principles. Each Tera template is passed a context
//! object. In the case of Rust, this object is a `struct` and must implement
//! `Serialize`. The fields of the context object are available in the context
//! of the template to be rendered.
#![feature(proc_macro_hygiene, decl_macro)]
pub mod error;
pub mod routes;
#[cfg(test)]
mod tests;
pub mod utils;
use log::{info, error};
use std::process;
use log::error;
use rocket::{catchers, routes, Rocket, Build, fs::FileServer};
use rocket_dyn_templates::Template;
fn main() {
// initialize the logger
use crate::routes::authentication::*;
use crate::routes::device::*;
use crate::routes::helpers::*;
use crate::routes::index::*;
use crate::routes::ping::*;
use crate::routes::scuttlebutt::*;
use crate::routes::settings::admin::*;
use crate::routes::settings::dns::*;
use crate::routes::settings::network::*;
pub type BoxError = Box<dyn std::error::Error>;
/// Create rocket instance & mount all routes.
fn init_rocket() -> Rocket<Build> {
rocket::build()
.mount(
"/",
routes![
add_credentials, // WEB ROUTE
connect_wifi, // WEB ROUTE
disconnect_wifi, // WEB ROUTE
deploy_ap, // WEB ROUTE
deploy_client, // WEB ROUTE
device_stats, // WEB ROUTE
forget_wifi, // WEB ROUTE
help, // WEB ROUTE
index, // WEB ROUTE
login, // WEB ROUTE
logout, // WEB ROUTE
messages, // WEB ROUTE
network_home, // WEB ROUTE
network_add_ssid, // WEB ROUTE
network_add_wifi, // WEB ROUTE
network_detail, // WEB ROUTE
peers, // WEB ROUTE
profile, // WEB ROUTE
reboot_cmd, // WEB ROUTE
shutdown_cmd, // WEB ROUTE
shutdown_menu, // WEB ROUTE
wifi_list, // WEB ROUTE
wifi_password, // WEB ROUTE
wifi_set_password, // WEB ROUTE
wifi_usage, // WEB ROUTE
wifi_usage_alerts, // WEB ROUTE
wifi_usage_reset, // WEB ROUTE
configure_dns, // WEB ROUTE
configure_dns_post, // WEB ROUTE
change_password, // WEB ROUTE
reset_password, // WEB ROUTE
reset_password_post, // WEB ROUTE
send_password_reset_page, // WEB ROUTE
send_password_reset_post, // WEB ROUTE
configure_admin, // WEB ROUTE
add_admin, // WEB ROUTE
add_admin_post, // WEB ROUTE
delete_admin_post, // WEB ROUTE
activate_ap, // JSON API
activate_client, // JSON API
add_wifi, // JSON API
connect_ap, // JSON API
disconnect_ap, // JSON API
forget_ap, // JSON API
modify_password, // JSON API
ping_pong, // JSON API
ping_network, // JSON API
ping_oled, // JSON API
ping_stats, // JSON API
reset_data_total, // JSON API
return_ip, // JSON API
return_rssi, // JSON API
return_ssid, // JSON API
return_state, // JSON API
return_status, // JSON API
reboot_device, // JSON API
scan_networks, // JSON API
shutdown_device, // JSON API
update_wifi_alerts, // JSON API
save_dns_configuration_endpoint, // JSON API
save_password_form_endpoint, // JSON API
reset_password_form_endpoint, // JSON API
],
)
.mount("/", FileServer::from("static"))
.register("/", catchers![not_found, internal_error])
glyph marked this conversation as resolved
Review

The new FileServer API / workflow is really nice.

The new `FileServer` API / workflow is really nice.
.attach(Template::fairing())
}
/// Launch the peach-web rocket server.
#[rocket::main]
async fn main() {
// initialize logger
env_logger::init();
// handle errors returned from `run`
if let Err(e) = peach_web::run() {
error!("Application error: {}", e);
// initialize rocket
info!("Initializing Rocket");
let rocket = init_rocket();
// launch rocket
info!("Launching Rocket");
if let Err(e) = rocket.launch().await {
error!("Error in Rocket application: {}", e);
process::exit(1);
}
}

View File

@ -1,14 +1,17 @@
use log::{debug, info};
use rocket::request::{FlashMessage, Form, FromForm};
use rocket::request::{FlashMessage};
use rocket::form::{Form, FromForm};
use rocket::response::{Flash, Redirect};
use rocket::{get, post};
use rocket_contrib::{json::Json, templates::Template};
use serde::{Deserialize, Serialize};
use rocket::serde::json::Json;
use rocket_dyn_templates::Template;
use rocket::serde::{Deserialize, Serialize};
use peach_lib::password_utils;
use crate::error::PeachWebError;
use crate::utils::{build_json_response, JsonResponse};
use crate::utils::build_json_response;
use rocket::serde::json::Value;
// HELPERS AND ROUTES FOR /login
@ -39,8 +42,8 @@ pub fn login(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("login", &context)
}
@ -138,8 +141,8 @@ pub fn reset_password(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("password/reset_password", &context)
}
@ -178,18 +181,18 @@ pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> Temp
#[post("/public/api/v1/reset_password", data = "<reset_password_form>")]
pub fn reset_password_form_endpoint(
reset_password_form: Json<ResetPasswordForm>,
) -> Json<JsonResponse> {
) -> Value {
let result = save_reset_password_form(reset_password_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "New password is now saved. Return home to login.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
@ -224,8 +227,8 @@ pub fn send_password_reset_page(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("password/send_password_reset", &context)
}
@ -295,8 +298,8 @@ pub fn change_password(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("password/change_password", &context)
}
@ -330,18 +333,18 @@ pub fn change_password_post(password_form: Form<PasswordForm>) -> Template {
/// JSON change password form request handler.
#[post("/api/v1/settings/change_password", data = "<password_form>")]
pub fn save_password_form_endpoint(password_form: Json<PasswordForm>) -> Json<JsonResponse> {
pub fn save_password_form_endpoint(password_form: Json<PasswordForm>) -> Value {
let result = save_password_form(password_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "Your password was successfully changed".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -4,7 +4,8 @@ use rocket::{
request::FlashMessage,
response::{Flash, Redirect},
};
use rocket_contrib::{json::Json, templates::Template};
use rocket_dyn_templates::Template;
use serde::Serialize;
use std::{
io,
@ -15,7 +16,8 @@ use peach_lib::config_manager::load_peach_config;
use peach_lib::stats_client::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat};
use peach_lib::{dyndns_client, network_client, oled_client, sbot_client, stats_client};
use crate::utils::{build_json_response, JsonResponse};
use crate::utils::build_json_response;
use rocket::serde::json::Value;
// HELPERS AND ROUTES FOR /device
@ -156,8 +158,8 @@ pub fn device_stats(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("device", &context)
@ -188,19 +190,19 @@ pub fn reboot_cmd() -> Flash<Redirect> {
/// JSON request handler for device reboot.
#[post("/api/v1/device/reboot")]
pub fn reboot_device() -> Json<JsonResponse> {
pub fn reboot_device() -> Value {
match reboot() {
Ok(_) => {
debug!("Going down for reboot...");
let status = "success".to_string();
let msg = "Going down for reboot.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Reboot failed");
let status = "error".to_string();
let msg = "Failed to reboot the device.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
@ -226,19 +228,19 @@ pub fn shutdown_cmd() -> Flash<Redirect> {
// shutdown the device
#[post("/api/v1/device/shutdown")]
pub fn shutdown_device() -> Json<JsonResponse> {
pub fn shutdown_device() -> Value {
match shutdown() {
Ok(_) => {
debug!("Going down for shutdown...");
let status = "success".to_string();
let msg = "Going down for shutdown.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Shutdown failed");
let status = "error".to_string();
let msg = "Failed to shutdown the device.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
@ -272,8 +274,8 @@ pub fn shutdown_menu(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("shutdown", &context)
}

View File

@ -1,13 +1,7 @@
use log::debug;
use rocket::{catch, get, response::NamedFile};
use rocket_contrib::templates::Template;
use rocket::{catch};
use rocket_dyn_templates::Template;
use serde::Serialize;
use std::path::{Path, PathBuf};
#[get("/<file..>", rank = 2)]
pub fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}
// HELPERS AND ROUTES FOR 404 ERROR

View File

@ -1,5 +1,5 @@
use rocket::{get, request::FlashMessage};
use rocket_contrib::templates::Template;
use rocket_dyn_templates::Template;
use serde::Serialize;
// HELPERS AND ROUTES FOR / (HOME PAGE)
@ -60,8 +60,8 @@ pub fn help(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("help", &context)
}

View File

@ -1,86 +1,76 @@
//! Helper routes for pinging services to check that they are active
use log::{debug, warn};
use rocket::get;
use rocket_contrib::json::Json;
use rocket::serde::json::{Value};
use peach_lib::dyndns_client::is_dns_updater_online;
use peach_lib::network_client;
use peach_lib::oled_client;
use peach_lib::stats_client;
use crate::utils::{build_json_response, JsonResponse};
use crate::utils::build_json_response;
/// Status route: useful for checking connectivity from web client.
#[get("/api/v1/ping")]
pub fn ping_pong() -> Json<JsonResponse> {
pub fn ping_pong() -> Value {
// ping pong
let status = "success".to_string();
let msg = "pong!".to_string();
Json(build_json_response(status, None, Some(msg)))
}
/// Test route: useful for ad hoc testing.
#[get("/api/v1/test")]
pub fn test_route() -> Json<JsonResponse> {
let val = is_dns_updater_online().unwrap();
let status = "success".to_string();
let msg = val.to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
/// Status route: check availability of `peach-network` microservice.
#[get("/api/v1/ping/network")]
pub fn ping_network() -> Json<JsonResponse> {
pub fn ping_network() -> Value {
glyph marked this conversation as resolved Outdated
Outdated
Review

Can we make this route name more specific? One option is to stick with the established ping route convention and use: /api/v1/ping/dyndns.

Can we make this route name more specific? One option is to stick with the established `ping` route convention and use: `/api/v1/ping/dyndns`.

I removed the test route,
-- fwiw the way I was using it was not as a test that needs to continue to exist,
but while building dyn_dns_updater,
a route that I could use to trigger the code I wanted to test.

Basically just a place where I can put arbitary code, and then call it by visiting that route.

But I will just temporarily re-add my lil test route when I want to use it for that, and it makes sense to not live in the codebase where it could cause confusion.

I removed the test route, -- fwiw the way I was using it was not as a test that needs to continue to exist, but while building dyn_dns_updater, a route that I could use to trigger the code I wanted to test. Basically just a place where I can put arbitary code, and then call it by visiting that route. But I will just temporarily re-add my lil test route when I want to use it for that, and it makes sense to not live in the codebase where it could cause confusion.
Outdated
Review

That makes sense to me. I can see the usefulness of a set of JSON /api/v1/diagnosis/... routes for these sorts of checks. It's really handy to have a programmatic and console-based way to query state.

That makes sense to me. I can see the usefulness of a set of JSON `/api/v1/diagnosis/...` routes for these sorts of checks. It's really handy to have a programmatic and console-based way to query state.
match network_client::ping() {
Ok(_) => {
glyph marked this conversation as resolved
Review

This is going to panic and crash the server if is_dns_updater_online returns an error. We should rather match on the Result and return an appropriate JSON Value if there's an error.

This is going to panic and crash the server if `is_dns_updater_online` returns an error. We should rather match on the `Result` and return an appropriate JSON `Value` if there's an error.
debug!("peach-network responded successfully");
let status = "success".to_string();
let msg = "peach-network is available.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("peach-network failed to respond");
let status = "error".to_string();
let msg = "peach-network is unavailable.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
/// Status route: check availability of `peach-oled` microservice.
#[get("/api/v1/ping/oled")]
pub fn ping_oled() -> Json<JsonResponse> {
pub fn ping_oled() -> Value {
match oled_client::ping() {
Ok(_) => {
debug!("peach-oled responded successfully");
let status = "success".to_string();
let msg = "peach-oled is available.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("peach-oled failed to respond");
let status = "error".to_string();
let msg = "peach-oled is unavailable.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
/// Status route: check availability of `peach-stats` microservice.
#[get("/api/v1/ping/stats")]
pub fn ping_stats() -> Json<JsonResponse> {
pub fn ping_stats() -> Value {
match stats_client::ping() {
Ok(_) => {
debug!("peach-stats responded successfully");
let status = "success".to_string();
let msg = "peach-stats is available.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("peach-stats failed to respond");
let status = "error".to_string();
let msg = "peach-stats is unavailable.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -1,7 +1,7 @@
//! Routes for ScuttleButt related functionality.
use rocket::{get, request::FlashMessage};
use rocket_contrib::templates::Template;
use rocket_dyn_templates::Template;
use serde::Serialize;
// HELPERS AND ROUTES FOR /messages
@ -33,8 +33,8 @@ pub fn messages(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("messages", &context)
}
@ -68,8 +68,8 @@ pub fn peers(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("peers", &context)
}
@ -103,8 +103,8 @@ pub fn profile(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("profile", &context)
}

View File

@ -1,11 +1,12 @@
use rocket::{
get, post,
request::{FlashMessage, Form, FromForm},
request::FlashMessage,
form::{Form, FromForm},
response::{Flash, Redirect},
uri,
};
use rocket_contrib::templates::Template;
use serde::{Deserialize, Serialize};
use rocket_dyn_templates::Template;
use rocket::serde::{Deserialize, Serialize};
use peach_lib::config_manager;
use peach_lib::config_manager::load_peach_config;
@ -47,8 +48,8 @@ pub fn configure_admin(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("admin/configure_admin", &context)
}
@ -93,8 +94,8 @@ pub fn add_admin(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("admin/add_admin", &context)

View File

@ -1,10 +1,12 @@
use log::info;
use rocket::{
get, post,
request::{FlashMessage, Form, FromForm},
request::FlashMessage,
form::{Form, FromForm}
};
use rocket_contrib::{json::Json, templates::Template};
use serde::{Deserialize, Serialize};
use rocket::serde::json::Json;
use rocket_dyn_templates::Template;
use rocket::serde::{Deserialize, Serialize};
use peach_lib::config_manager;
use peach_lib::config_manager::load_peach_config;
@ -18,7 +20,8 @@ use peach_lib::jsonrpc_client_core::{Error, ErrorKind};
use peach_lib::jsonrpc_core::types::error::ErrorCode;
use crate::error::PeachWebError;
use crate::utils::{build_json_response, JsonResponse};
use crate::utils::build_json_response;
use rocket::serde::json::Value;
#[derive(Debug, Deserialize, FromForm)]
pub struct DnsForm {
@ -118,8 +121,8 @@ pub fn configure_dns(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
Template::render("configure_dns", &context)
}
@ -150,18 +153,18 @@ pub fn configure_dns_post(dns: Form<DnsForm>) -> Template {
}
#[post("/api/v1/dns/configure", data = "<dns_form>")]
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>) -> Json<JsonResponse> {
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>) -> Value {
let result = save_dns_configuration(dns_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "New dynamic dns configuration is now enabled".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -1,15 +1,16 @@
use log::{debug, warn};
use percent_encoding::percent_decode;
use rocket::{
get,
http::RawStr,
post,
request::{FlashMessage, Form, FromForm},
request::FlashMessage,
form::{Form, FromForm},
response::{Flash, Redirect},
uri, UriDisplayQuery,
};
use rocket_contrib::{json, json::Json, templates::Template};
use serde::{Deserialize, Serialize};
use rocket::serde::json::{json, Json};
use rocket_dyn_templates::Template;
use rocket::serde::{Deserialize, Serialize};
use std::collections::HashMap;
use peach_lib::network_client;
@ -18,7 +19,8 @@ use peach_lib::stats_client::Traffic;
use crate::utils::monitor;
use crate::utils::monitor::{Alert, Data, Threshold};
use crate::utils::{build_json_response, JsonResponse};
use crate::utils::build_json_response;
use rocket::serde::json::Value;
// STRUCTS USED BY NETWORK ROUTES
@ -50,7 +52,7 @@ pub fn wifi_usage_reset() -> Flash<Redirect> {
#[post("/network/wifi/connect", data = "<network>")]
pub fn connect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
let ssid = &network.ssid;
let url = uri!(network_detail: ssid);
let url = uri!(network_detail(ssid = ssid));
match network_client::id("wlan0", ssid) {
Ok(id) => match network_client::connect(&id, "wlan0") {
Ok(_) => Flash::success(Redirect::to(url), "Connected to chosen network"),
@ -84,21 +86,19 @@ pub fn forget_wifi(network: Form<Ssid>) -> Flash<Redirect> {
}
#[get("/network/wifi/modify?<ssid>")]
pub fn wifi_password(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
// decode ssid from url
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
pub fn wifi_password(ssid: &str, flash: Option<FlashMessage>) -> Template {
let mut context = NetworkAddContext {
back: Some("/network/wifi".to_string()),
flash_name: None,
flash_msg: None,
selected: Some(decoded_ssid.to_string()),
selected: Some(ssid.to_string()),
title: Some("Update WiFi Password".to_string()),
};
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("network_modify", &context)
@ -108,7 +108,7 @@ pub fn wifi_password(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
pub fn wifi_set_password(wifi: Form<WiFi>) -> Flash<Redirect> {
let ssid = &wifi.ssid;
let pass = &wifi.pass;
let url = uri!(network_detail: ssid);
let url = uri!(network_detail(ssid = ssid));
match network_client::update("wlan0", ssid, pass) {
Ok(_) => Flash::success(Redirect::to(url), "WiFi password updated".to_string()),
Err(_) => Flash::error(
@ -283,8 +283,8 @@ pub fn network_home(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("network_card", &context)
@ -383,8 +383,8 @@ pub fn wifi_list(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("network_list", &context)
@ -540,19 +540,17 @@ impl NetworkDetailContext {
}
#[get("/network/wifi?<ssid>")]
pub fn network_detail(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
pub fn network_detail(ssid: &str, flash: Option<FlashMessage>) -> Template {
// assign context through context_builder call
let mut context = NetworkDetailContext::build();
context.back = Some("/network/wifi".to_string());
context.title = Some("WiFi Network".to_string());
// decode ssid from url
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
context.selected = Some(decoded_ssid.to_string());
context.selected = Some(ssid.to_string());
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("network_detail", &context)
@ -581,8 +579,8 @@ pub fn network_add_wifi(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("network_add", &context)
@ -611,18 +609,16 @@ impl NetworkAddContext {
}
#[get("/network/wifi/add?<ssid>")]
pub fn network_add_ssid(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
// decode ssid from url
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
pub fn network_add_ssid(ssid: &str, flash: Option<FlashMessage>) -> Template {
let mut context = NetworkAddContext::build();
context.back = Some("/network/wifi".to_string());
context.selected = Some(decoded_ssid.to_string());
context.selected = Some(ssid.to_string());
glyph marked this conversation as resolved Outdated
Outdated
Review

Was this commented-out line left in by mistake?

Was this commented-out line left in by mistake?

yup this was a mistake, good catch

yup this was a mistake, good catch
context.title = Some("Add WiFi Network".to_string());
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("network_add", &context)
@ -731,8 +727,8 @@ pub fn wifi_usage(flash: Option<FlashMessage>) -> Template {
// check to see if there is a flash message to display
if let Some(flash) = flash {
// add flash message contents to the context object
context.flash_name = Some(flash.name().to_string());
context.flash_msg = Some(flash.msg().to_string());
context.flash_name = Some(flash.kind().to_string());
context.flash_msg = Some(flash.message().to_string());
};
// template_dir is set in Rocket.toml
Template::render("network_usage", &context)
@ -759,25 +755,25 @@ pub fn wifi_usage_alerts(thresholds: Form<Threshold>) -> Flash<Redirect> {
}
#[post("/api/v1/network/wifi/usage", data = "<thresholds>")]
pub fn update_wifi_alerts(thresholds: Json<Threshold>) -> Json<JsonResponse> {
pub fn update_wifi_alerts(thresholds: Json<Threshold>) -> Value {
match monitor::update_store(thresholds.into_inner()) {
Ok(_) => {
debug!("WiFi data usage thresholds updated.");
let status = "success".to_string();
let msg = "Updated alert threshold and flags.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Failed to update WiFi data usage thresholds.");
let status = "error".to_string();
let msg = "Failed to update WiFi data usage thresholds.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[post("/api/v1/network/wifi/usage/reset")]
pub fn reset_data_total() -> Json<JsonResponse> {
pub fn reset_data_total() -> Value {
match monitor::reset_data() {
Ok(_) => {
debug!("Reset network data usage total.");
@ -795,13 +791,13 @@ pub fn reset_data_total() -> Json<JsonResponse> {
let data = json!(current_traffic);
let status = "success".to_string();
let msg = "Reset network data usage total.".to_string();
Json(build_json_response(status, Some(data), Some(msg)))
build_json_response(status, Some(data), Some(msg))
}
Err(_) => {
warn!("Failed to reset network data usage total.");
let status = "error".to_string();
let msg = "Failed to reset network data usage total.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
@ -809,18 +805,18 @@ pub fn reset_data_total() -> Json<JsonResponse> {
// HELPERS AND ROUTES FOR ACCESS POINT ACTIVATION
#[post("/api/v1/network/activate_ap")]
pub fn activate_ap() -> Json<JsonResponse> {
pub fn activate_ap() -> Value {
// activate the wireless access point
debug!("Activating WiFi access point.");
match network_client::activate_ap() {
Ok(_) => {
let status = "success".to_string();
Json(build_json_response(status, None, None))
build_json_response(status, None, None)
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to activate WiFi access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
@ -828,24 +824,24 @@ pub fn activate_ap() -> Json<JsonResponse> {
// HELPERS AND ROUTES FOR WIFI CLIENT MANAGEMENT
#[post("/api/v1/network/activate_client")]
pub fn activate_client() -> Json<JsonResponse> {
pub fn activate_client() -> Value {
// activate the wireless client
debug!("Activating WiFi client mode.");
match network_client::activate_client() {
Ok(_) => {
let status = "success".to_string();
Json(build_json_response(status, None, None))
build_json_response(status, None, None)
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to activate WiFi client mode.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[post("/api/v1/network/wifi", data = "<wifi>")]
pub fn add_wifi(wifi: Json<WiFi>) -> Json<JsonResponse> {
pub fn add_wifi(wifi: Json<WiFi>) -> Value {
// generate and write wifi config to wpa_supplicant
match network_client::add(&wifi.ssid, &wifi.pass) {
Ok(_) => {
@ -858,20 +854,20 @@ pub fn add_wifi(wifi: Json<WiFi>) -> Json<JsonResponse> {
// json response for successful update
let status = "success".to_string();
let msg = "WiFi credentials added.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
debug!("Failed to add WiFi credentials.");
// json response for failed update
let status = "error".to_string();
let msg = "Failed to add WiFi credentials.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[post("/api/v1/network/wifi/connect", data = "<ssid>")]
pub fn connect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
pub fn connect_ap(ssid: Json<Ssid>) -> Value {
// retrieve the id for the given network ssid
match network_client::id("wlan0", &ssid.ssid) {
// attempt connection with the given network
@ -879,60 +875,60 @@ pub fn connect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
Ok(_) => {
let status = "success".to_string();
let msg = "Connected to chosen network.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to connect to chosen network.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
},
Err(_) => {
let status = "error".to_string();
let msg = "Failed to retrieve the network ID.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[post("/api/v1/network/wifi/disconnect", data = "<ssid>")]
pub fn disconnect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
pub fn disconnect_ap(ssid: Json<Ssid>) -> Value {
// attempt to disable the current network for wlan0 interface
match network_client::disable("wlan0", &ssid.ssid) {
Ok(_) => {
let status = "success".to_string();
let msg = "Disconnected from WiFi network.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to disconnect from WiFi network.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[post("/api/v1/network/wifi/forget", data = "<network>")]
pub fn forget_ap(network: Json<Ssid>) -> Json<JsonResponse> {
pub fn forget_ap(network: Json<Ssid>) -> Value {
let ssid = &network.ssid;
match network_client::forget("wlan0", ssid) {
Ok(_) => {
debug!("Removed WiFi credentials for chosen network.");
let status = "success".to_string();
let msg = "WiFi network credentials removed.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Failed to remove WiFi credentials.");
let status = "error".to_string();
let msg = "Failed to remove WiFi network credentials.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[post("/api/v1/network/wifi/modify", data = "<wifi>")]
pub fn modify_password(wifi: Json<WiFi>) -> Json<JsonResponse> {
pub fn modify_password(wifi: Json<WiFi>) -> Value {
let ssid = &wifi.ssid;
let pass = &wifi.pass;
// we are using a helper function (`update`) to delete the old
@ -943,13 +939,13 @@ pub fn modify_password(wifi: Json<WiFi>) -> Json<JsonResponse> {
debug!("WiFi password updated for chosen network.");
let status = "success".to_string();
let msg = "WiFi password updated.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Failed to update WiFi password.");
let status = "error".to_string();
let msg = "Failed to update WiFi password.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
@ -957,7 +953,7 @@ pub fn modify_password(wifi: Json<WiFi>) -> Json<JsonResponse> {
// HELPERS AND ROUTES FOR NETWORK STATE QUERIES
#[get("/api/v1/network/ip")]
pub fn return_ip() -> Json<JsonResponse> {
pub fn return_ip() -> Value {
// retrieve ip for wlan0 or set to x.x.x.x if not found
let wlan_ip = match network_client::ip("wlan0") {
Ok(ip) => ip,
@ -973,45 +969,45 @@ pub fn return_ip() -> Json<JsonResponse> {
"ap0": ap_ip
});
let status = "success".to_string();
Json(build_json_response(status, Some(data), None))
build_json_response(status, Some(data), None)
}
#[get("/api/v1/network/rssi")]
pub fn return_rssi() -> Json<JsonResponse> {
pub fn return_rssi() -> Value {
// retrieve rssi for connected network
match network_client::rssi("wlan0") {
Ok(rssi) => {
let status = "success".to_string();
let data = json!(rssi);
Json(build_json_response(status, Some(data), None))
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[get("/api/v1/network/ssid")]
pub fn return_ssid() -> Json<JsonResponse> {
pub fn return_ssid() -> Value {
// retrieve ssid for connected network
match network_client::ssid("wlan0") {
Ok(network) => {
let status = "success".to_string();
let data = json!(network);
Json(build_json_response(status, Some(data), None))
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[get("/api/v1/network/state")]
pub fn return_state() -> Json<JsonResponse> {
pub fn return_state() -> Value {
// retrieve state of wlan0 or set to x.x.x.x if not found
let wlan_state = match network_client::state("wlan0") {
Ok(state) => state,
@ -1027,39 +1023,39 @@ pub fn return_state() -> Json<JsonResponse> {
"ap0": ap_state
});
let status = "success".to_string();
Json(build_json_response(status, Some(data), None))
build_json_response(status, Some(data), None)
}
#[get("/api/v1/network/status")]
pub fn return_status() -> Json<JsonResponse> {
pub fn return_status() -> Value {
// retrieve status info for wlan0 interface
match network_client::status("wlan0") {
Ok(network) => {
let status = "success".to_string();
let data = json!(network);
Json(build_json_response(status, Some(data), None))
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}
#[get("/api/v1/network/wifi")]
pub fn scan_networks() -> Json<JsonResponse> {
pub fn scan_networks() -> Value {
// retrieve scan results for access-points within range of wlan0
match network_client::available_networks("wlan0") {
Ok(networks) => {
let status = "success".to_string();
let data = json!(networks);
Json(build_json_response(status, Some(data), None))
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Unable to scan for networks. Interface may be deactivated.".to_string();
Json(build_json_response(status, None, Some(msg)))
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -1,24 +1,24 @@
use std::fs::File;
use std::io::Read;
use rocket::serde::json::{Value, json};
use rocket::http::{ContentType, Status};
use rocket::local::Client;
use rocket_contrib::json;
use rocket::local::blocking::Client;
use crate::utils::build_json_response;
use super::rocket;
use super::init_rocket;
// helper function to test correct retrieval and content of a file
fn test_query_file<T>(path: &str, file: T, status: Status)
where
T: Into<Option<&'static str>>,
{
let client = Client::new(rocket()).unwrap();
let mut response = client.get(path).dispatch();
let client = Client::tracked(init_rocket()).unwrap();
let response = client.get(path).dispatch();
assert_eq!(response.status(), status);
let body_data = response.body().and_then(|body| body.into_bytes());
let body_data = response.into_bytes();
if let Some(filename) = file.into() {
let expected_data = read_file_content(filename);
assert!(body_data.map_or(false, |s| s == expected_data));
@ -39,11 +39,11 @@ fn read_file_content(path: &str) -> Vec<u8> {
#[test]
fn index_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("/peers"));
assert!(body.contains("/profile"));
assert!(body.contains("/messages"));
@ -54,11 +54,11 @@ fn index_html() {
#[test]
fn network_card_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/network").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("MODE"));
assert!(body.contains("SSID"));
assert!(body.contains("IP"));
@ -72,11 +72,11 @@ fn network_card_html() {
#[test]
fn network_list_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/network/wifi").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network/wifi").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("WiFi Networks"));
assert!(body.contains("No saved or available networks found."));
}
@ -84,21 +84,21 @@ fn network_list_html() {
// TODO: needs further testing once template has been refactored
#[test]
fn network_detail_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network/wifi?ssid=Home").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
//let body = response.body_string().unwrap();
//let body = response.into_string().unwrap();
//assert!(body.contains("Network not found"));
}
#[test]
fn network_add_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/network/wifi/add").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network/wifi/add").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Add WiFi Network"));
assert!(body.contains("SSID"));
assert!(body.contains("Password"));
@ -108,11 +108,11 @@ fn network_add_html() {
#[test]
fn network_add_ssid_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/network/wifi/add?ssid=Home").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network/wifi/add?ssid=Home").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Add WiFi Network"));
assert!(body.contains("Home"));
assert!(body.contains("Password"));
@ -122,11 +122,11 @@ fn network_add_ssid_html() {
#[test]
fn device_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/device").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/device").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Device Status"));
assert!(body.contains("Networking"));
assert!(body.contains("Display"));
@ -135,71 +135,71 @@ fn device_html() {
#[test]
fn help_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/help").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/help").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Help"));
}
#[test]
fn login_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/login").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/login").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Login"));
}
#[test]
fn messages_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/messages").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/messages").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Private Messages"));
}
#[test]
fn peers_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/peers").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/peers").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Scuttlebutt Peers"));
}
#[test]
fn profile_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/profile").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/profile").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Profile"));
}
#[test]
fn shutdown_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/shutdown").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/shutdown").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Shutdown Device"));
}
#[test]
fn network_usage_html() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/network/wifi/usage").dispatch();
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network/wifi/usage").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::HTML));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Network Data Usage"));
assert!(body.contains("WARNING THRESHOLD"));
assert!(body.contains("Update"));
@ -208,7 +208,7 @@ fn network_usage_html() {
#[test]
fn add_credentials() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/network/wifi/add")
.header(ContentType::Form)
@ -220,7 +220,7 @@ fn add_credentials() {
#[test]
fn forget_wifi() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/network/wifi/forget")
.header(ContentType::Form)
@ -232,7 +232,7 @@ fn forget_wifi() {
#[test]
fn modify_password() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/network/wifi/modify")
.header(ContentType::Form)
@ -244,7 +244,7 @@ fn modify_password() {
#[test]
fn deploy_ap() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network/ap/activate").dispatch();
// check for 303 status (redirect)
assert_eq!(response.status(), Status::SeeOther);
@ -253,7 +253,7 @@ fn deploy_ap() {
#[test]
fn deploy_client() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client.get("/network/wifi/activate").dispatch();
// check for 303 status (redirect)
assert_eq!(response.status(), Status::SeeOther);
@ -264,7 +264,7 @@ fn deploy_client() {
#[test]
fn activate_ap() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/api/v1/network/activate_ap")
.header(ContentType::JSON)
@ -275,7 +275,7 @@ fn activate_ap() {
#[test]
fn activate_client() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/api/v1/network/activate_client")
.header(ContentType::JSON)
@ -286,54 +286,54 @@ fn activate_client() {
#[test]
fn return_ip() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.get("/api/v1/network/ip")
.header(ContentType::JSON)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("wlan0"));
assert!(body.contains("ap0"));
}
#[test]
fn return_rssi() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.get("/api/v1/network/rssi")
.header(ContentType::JSON)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Not currently connected to an access point."));
}
#[test]
fn return_ssid() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.get("/api/v1/network/ssid")
.header(ContentType::JSON)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Not currently connected to an access point."));
}
#[test]
fn return_state() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.get("/api/v1/network/state")
.header(ContentType::JSON)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("ap0"));
assert!(body.contains("wlan0"));
assert!(body.contains("unavailable"));
@ -341,82 +341,82 @@ fn return_state() {
#[test]
fn return_status() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.get("/api/v1/network/status")
.header(ContentType::JSON)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Not currently connected to an access point."));
}
#[test]
fn scan_networks() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.get("/api/v1/network/wifi")
.header(ContentType::JSON)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Unable to scan for networks. Interface may be deactivated."));
}
#[test]
fn add_wifi() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/api/v1/network/wifi")
.header(ContentType::JSON)
.body(r#"{ "ssid": "Home", "pass": "Password" }"#)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Failed to add WiFi credentials."));
}
#[test]
fn remove_wifi() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/api/v1/network/wifi/forget")
.header(ContentType::JSON)
.body(r#"{ "ssid": "Home" }"#)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Failed to remove WiFi network credentials."));
}
#[test]
fn new_password() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.post("/api/v1/network/wifi/modify")
.header(ContentType::JSON)
.body(r#"{ "ssid": "Home", "pass": "Password" }"#)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("Failed to update WiFi password."));
}
#[test]
fn ping_pong() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
let response = client
.get("/api/v1/ping")
.header(ContentType::JSON)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::JSON));
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
assert!(body.contains("pong!"));
}
@ -426,12 +426,13 @@ fn ping_pong() {
fn test_build_json_response() {
let status = "success".to_string();
let data = json!("WiFi credentials added.".to_string());
let json = build_json_response(status, Some(data), None);
assert_eq!(json.status, "success");
assert_eq!(json.data, Some(json!("WiFi credentials added.")));
assert_eq!(json.msg, None);
let j: Value = build_json_response(status, Some(data), None);
assert_eq!(j["status"], "success");
assert_eq!(j["data"], "WiFi credentials added.");
assert_eq!(j["msg"], json!(null));
}
// FILE TESTS
#[test]
@ -471,16 +472,16 @@ fn invalid_path() {
#[test]
fn invalid_get_request() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(init_rocket()).unwrap();
// try to get a path that doesn't exist
let mut res = client
let res = client
.get("/message/99")
.header(ContentType::JSON)
.dispatch();
assert_eq!(res.status(), Status::NotFound);
let body = res.body_string().unwrap();
let body = res.into_string().unwrap();
assert!(body.contains("404: Page Not Found"));
assert!(body.contains("No PeachCloud resource exists for this URL."));
}

View File

@ -1,25 +1,16 @@
pub mod monitor;
use rocket_contrib::json::JsonValue;
use serde::Serialize;
use rocket::serde::json::{Value, json};
use rocket::serde::{Serialize};
// HELPER FUNCTIONS
#[derive(Serialize)]
pub struct JsonResponse {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<JsonValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub msg: Option<String>,
}
pub fn build_json_response(
status: String,
data: Option<JsonValue>,
data: Option<Value>,
msg: Option<String>,
) -> JsonResponse {
JsonResponse { status, data, msg }
) -> Value {
json!({ "status": status, "data": data, "msg": msg })
}
#[derive(Debug, Serialize)]

View File

@ -3,8 +3,8 @@
use std::convert::TryInto;
use nest::{Error, Store, Value};
use rocket::request::FromForm;
use serde::{Deserialize, Serialize};
use rocket::form::{FromForm};
use rocket::serde::{Deserialize, Serialize};
use serde_json::json;
/// Network traffic data total

View File

@ -1,65 +0,0 @@
// NOTE: websockets are not currently in use for PeachCloud but may be in the
// future.
use std::io;
use std::thread;
use log::{debug, info};
use websocket::sync::Server;
use websocket::{Message, OwnedMessage};
pub fn websocket_server(address: String) -> io::Result<()> {
// start listening for WebSocket connections
let ws_server = Server::bind(address)?;
info!("Listening for WebSocket connections.");
for connection in ws_server.filter_map(Result::ok) {
// spawn a new thread for each connection
thread::spawn(move || {
if !connection
.protocols()
.contains(&"rust-websocket".to_string())
{
connection.reject().unwrap();
return;
}
let mut client = connection.use_protocol("rust-websocket").accept().unwrap();
let client_ip = client.peer_addr().unwrap();
debug!("Websocket connection from {}.", client_ip);
let msg_text = "Websocket successfully connected.".to_string();
let message = Message::text(msg_text);
client.send_message(&message).unwrap();
let (mut receiver, mut sender) = client.split().unwrap();
for message in receiver.incoming_messages() {
let message = message.unwrap();
match message {
OwnedMessage::Close(_) => {
debug!("Received close message.");
let message = Message::close();
sender.send_message(&message).unwrap();
debug!("Websocket client {} disconnected.", client_ip);
return;
}
OwnedMessage::Ping(data) => {
debug!("Received ping message.");
let message = Message::pong(data);
sender.send_message(&message).unwrap();
}
_ => {
sender.send_message(&message).unwrap();
debug!("Received unknown message: {:?}", message);
}
}
}
});
}
Ok(())
}