Upgrade peach-web to use rocket 0.5 #15
1693
Cargo.lock
generated
@ -40,7 +40,7 @@ log = "0.4"
|
|||||||
nest = "1.0.0"
|
nest = "1.0.0"
|
||||||
peach-lib = { path = "../peach-lib" }
|
peach-lib = { path = "../peach-lib" }
|
||||||
percent-encoding = "2.1.0"
|
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 = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
snafu = "0.6"
|
snafu = "0.6"
|
||||||
@ -50,7 +50,6 @@ regex = "1"
|
|||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_dyn_templates]
|
||||||
version = "0.4.10"
|
version = "0.1.0-rc.1"
|
||||||
default-features = false
|
features = ["tera"]
|
||||||
features = ["json", "tera_templates"]
|
|
||||||
|
@ -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(())
|
|
||||||
}
|
|
@ -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 std::process;
|
||||||
|
|
||||||
use log::error;
|
use rocket::{catchers, routes, Rocket, Build, fs::FileServer};
|
||||||
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
fn main() {
|
use crate::routes::authentication::*;
|
||||||
// initialize the logger
|
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
|
|||||||
|
.attach(Template::fairing())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Launch the peach-web rocket server.
|
||||||
|
#[rocket::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
|
// initialize logger
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
// handle errors returned from `run`
|
// initialize rocket
|
||||||
if let Err(e) = peach_web::run() {
|
info!("Initializing Rocket");
|
||||||
error!("Application error: {}", e);
|
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);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
use log::{debug, info};
|
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::response::{Flash, Redirect};
|
||||||
use rocket::{get, post};
|
use rocket::{get, post};
|
||||||
use rocket_contrib::{json::Json, templates::Template};
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket_dyn_templates::Template;
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use peach_lib::password_utils;
|
use peach_lib::password_utils;
|
||||||
|
|
||||||
use crate::error::PeachWebError;
|
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
|
// 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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("login", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("password/reset_password", &context)
|
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>")]
|
#[post("/public/api/v1/reset_password", data = "<reset_password_form>")]
|
||||||
pub fn reset_password_form_endpoint(
|
pub fn reset_password_form_endpoint(
|
||||||
reset_password_form: Json<ResetPasswordForm>,
|
reset_password_form: Json<ResetPasswordForm>,
|
||||||
) -> Json<JsonResponse> {
|
) -> Value {
|
||||||
let result = save_reset_password_form(reset_password_form.into_inner());
|
let result = save_reset_password_form(reset_password_form.into_inner());
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "New password is now saved. Return home to login.".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) => {
|
Err(err) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = format!("{}", err);
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("password/send_password_reset", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("password/change_password", &context)
|
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.
|
/// JSON change password form request handler.
|
||||||
#[post("/api/v1/settings/change_password", data = "<password_form>")]
|
#[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());
|
let result = save_password_form(password_form.into_inner());
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Your password was successfully changed".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) => {
|
Err(err) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = format!("{}", err);
|
let msg = format!("{}", err);
|
||||||
Json(build_json_response(status, None, Some(msg)))
|
build_json_response(status, None, Some(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,8 @@ use rocket::{
|
|||||||
request::FlashMessage,
|
request::FlashMessage,
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
};
|
};
|
||||||
use rocket_contrib::{json::Json, templates::Template};
|
|
||||||
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
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::stats_client::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat};
|
||||||
use peach_lib::{dyndns_client, network_client, oled_client, sbot_client, stats_client};
|
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
|
// 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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("device", &context)
|
Template::render("device", &context)
|
||||||
@ -188,19 +190,19 @@ pub fn reboot_cmd() -> Flash<Redirect> {
|
|||||||
|
|
||||||
/// JSON request handler for device reboot.
|
/// JSON request handler for device reboot.
|
||||||
#[post("/api/v1/device/reboot")]
|
#[post("/api/v1/device/reboot")]
|
||||||
pub fn reboot_device() -> Json<JsonResponse> {
|
pub fn reboot_device() -> Value {
|
||||||
match reboot() {
|
match reboot() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Going down for reboot...");
|
debug!("Going down for reboot...");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Going down for reboot.".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(_) => {
|
Err(_) => {
|
||||||
warn!("Reboot failed");
|
warn!("Reboot failed");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to reboot the device.".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
|
// shutdown the device
|
||||||
#[post("/api/v1/device/shutdown")]
|
#[post("/api/v1/device/shutdown")]
|
||||||
pub fn shutdown_device() -> Json<JsonResponse> {
|
pub fn shutdown_device() -> Value {
|
||||||
match shutdown() {
|
match shutdown() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Going down for shutdown...");
|
debug!("Going down for shutdown...");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Going down for shutdown.".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(_) => {
|
Err(_) => {
|
||||||
warn!("Shutdown failed");
|
warn!("Shutdown failed");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to shutdown the device.".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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("shutdown", &context)
|
Template::render("shutdown", &context)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use rocket::{catch, get, response::NamedFile};
|
use rocket::{catch};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Serialize;
|
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
|
// HELPERS AND ROUTES FOR 404 ERROR
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use rocket::{get, request::FlashMessage};
|
use rocket::{get, request::FlashMessage};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR / (HOME PAGE)
|
// 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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("help", &context)
|
Template::render("help", &context)
|
||||||
}
|
}
|
||||||
|
@ -1,86 +1,76 @@
|
|||||||
//! Helper routes for pinging services to check that they are active
|
//! Helper routes for pinging services to check that they are active
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use rocket::get;
|
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::network_client;
|
||||||
use peach_lib::oled_client;
|
use peach_lib::oled_client;
|
||||||
use peach_lib::stats_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.
|
/// Status route: useful for checking connectivity from web client.
|
||||||
#[get("/api/v1/ping")]
|
#[get("/api/v1/ping")]
|
||||||
pub fn ping_pong() -> Json<JsonResponse> {
|
pub fn ping_pong() -> Value {
|
||||||
// ping pong
|
// ping pong
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "pong!".to_string();
|
let msg = "pong!".to_string();
|
||||||
Json(build_json_response(status, None, Some(msg)))
|
build_json_response(status, None, Some(msg))
|
||||||
}
|
|
||||||
|
|
||||||
/// Test route: useful for ad hoc testing.
|
|
||||||
#[get("/api/v1/test")]
|
|
||||||
pub fn test_route() -> Json<JsonResponse> {
|
|
||||||
let val = is_dns_updater_online().unwrap();
|
|
||||||
let status = "success".to_string();
|
|
||||||
let msg = val.to_string();
|
|
||||||
Json(build_json_response(status, None, Some(msg)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Status route: check availability of `peach-network` microservice.
|
/// Status route: check availability of `peach-network` microservice.
|
||||||
#[get("/api/v1/ping/network")]
|
#[get("/api/v1/ping/network")]
|
||||||
pub fn ping_network() -> Json<JsonResponse> {
|
pub fn ping_network() -> Value {
|
||||||
glyph marked this conversation as resolved
Outdated
glyph
commented
Can we make this route name more specific? One option is to stick with the established 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`.
notplants
commented
I removed the test route, 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.
glyph
commented
That makes sense to me. I can see the usefulness of a set of JSON 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() {
|
match network_client::ping() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
glyph marked this conversation as resolved
glyph
commented
This is going to panic and crash the server if 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");
|
debug!("peach-network responded successfully");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "peach-network is available.".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(_) => {
|
Err(_) => {
|
||||||
warn!("peach-network failed to respond");
|
warn!("peach-network failed to respond");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "peach-network is unavailable.".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.
|
/// Status route: check availability of `peach-oled` microservice.
|
||||||
#[get("/api/v1/ping/oled")]
|
#[get("/api/v1/ping/oled")]
|
||||||
pub fn ping_oled() -> Json<JsonResponse> {
|
pub fn ping_oled() -> Value {
|
||||||
match oled_client::ping() {
|
match oled_client::ping() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("peach-oled responded successfully");
|
debug!("peach-oled responded successfully");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "peach-oled is available.".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(_) => {
|
Err(_) => {
|
||||||
warn!("peach-oled failed to respond");
|
warn!("peach-oled failed to respond");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "peach-oled is unavailable.".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.
|
/// Status route: check availability of `peach-stats` microservice.
|
||||||
#[get("/api/v1/ping/stats")]
|
#[get("/api/v1/ping/stats")]
|
||||||
pub fn ping_stats() -> Json<JsonResponse> {
|
pub fn ping_stats() -> Value {
|
||||||
match stats_client::ping() {
|
match stats_client::ping() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("peach-stats responded successfully");
|
debug!("peach-stats responded successfully");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "peach-stats is available.".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(_) => {
|
Err(_) => {
|
||||||
warn!("peach-stats failed to respond");
|
warn!("peach-stats failed to respond");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "peach-stats is unavailable.".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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Routes for ScuttleButt related functionality.
|
//! Routes for ScuttleButt related functionality.
|
||||||
|
|
||||||
use rocket::{get, request::FlashMessage};
|
use rocket::{get, request::FlashMessage};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR /messages
|
// 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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("messages", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("peers", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("profile", &context)
|
Template::render("profile", &context)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use rocket::{
|
use rocket::{
|
||||||
get, post,
|
get, post,
|
||||||
request::{FlashMessage, Form, FromForm},
|
request::FlashMessage,
|
||||||
|
form::{Form, FromForm},
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
uri,
|
uri,
|
||||||
};
|
};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use peach_lib::config_manager;
|
use peach_lib::config_manager;
|
||||||
use peach_lib::config_manager::load_peach_config;
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("admin/configure_admin", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("admin/add_admin", &context)
|
Template::render("admin/add_admin", &context)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
get, post,
|
get, post,
|
||||||
request::{FlashMessage, Form, FromForm},
|
request::FlashMessage,
|
||||||
|
form::{Form, FromForm}
|
||||||
};
|
};
|
||||||
use rocket_contrib::{json::Json, templates::Template};
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket_dyn_templates::Template;
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use peach_lib::config_manager;
|
use peach_lib::config_manager;
|
||||||
use peach_lib::config_manager::load_peach_config;
|
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 peach_lib::jsonrpc_core::types::error::ErrorCode;
|
||||||
|
|
||||||
use crate::error::PeachWebError;
|
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)]
|
#[derive(Debug, Deserialize, FromForm)]
|
||||||
pub struct DnsForm {
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
Template::render("configure_dns", &context)
|
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>")]
|
#[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());
|
let result = save_dns_configuration(dns_form.into_inner());
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "New dynamic dns configuration is now enabled".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) => {
|
Err(err) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = format!("{}", err);
|
let msg = format!("{}", err);
|
||||||
Json(build_json_response(status, None, Some(msg)))
|
build_json_response(status, None, Some(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use percent_encoding::percent_decode;
|
|
||||||
use rocket::{
|
use rocket::{
|
||||||
get,
|
get,
|
||||||
http::RawStr,
|
|
||||||
post,
|
post,
|
||||||
request::{FlashMessage, Form, FromForm},
|
request::FlashMessage,
|
||||||
|
form::{Form, FromForm},
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
uri, UriDisplayQuery,
|
uri, UriDisplayQuery,
|
||||||
};
|
};
|
||||||
use rocket_contrib::{json, json::Json, templates::Template};
|
use rocket::serde::json::{json, Json};
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket_dyn_templates::Template;
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use peach_lib::network_client;
|
use peach_lib::network_client;
|
||||||
@ -18,7 +19,8 @@ use peach_lib::stats_client::Traffic;
|
|||||||
|
|
||||||
use crate::utils::monitor;
|
use crate::utils::monitor;
|
||||||
use crate::utils::monitor::{Alert, Data, Threshold};
|
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
|
// STRUCTS USED BY NETWORK ROUTES
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ pub fn wifi_usage_reset() -> Flash<Redirect> {
|
|||||||
#[post("/network/wifi/connect", data = "<network>")]
|
#[post("/network/wifi/connect", data = "<network>")]
|
||||||
pub fn connect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
|
pub fn connect_wifi(network: Form<Ssid>) -> Flash<Redirect> {
|
||||||
let ssid = &network.ssid;
|
let ssid = &network.ssid;
|
||||||
let url = uri!(network_detail: ssid);
|
let url = uri!(network_detail(ssid = ssid));
|
||||||
match network_client::id("wlan0", ssid) {
|
match network_client::id("wlan0", ssid) {
|
||||||
Ok(id) => match network_client::connect(&id, "wlan0") {
|
Ok(id) => match network_client::connect(&id, "wlan0") {
|
||||||
Ok(_) => Flash::success(Redirect::to(url), "Connected to chosen network"),
|
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>")]
|
#[get("/network/wifi/modify?<ssid>")]
|
||||||
pub fn wifi_password(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
|
pub fn wifi_password(ssid: &str, flash: Option<FlashMessage>) -> Template {
|
||||||
// decode ssid from url
|
|
||||||
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
|
|
||||||
let mut context = NetworkAddContext {
|
let mut context = NetworkAddContext {
|
||||||
back: Some("/network/wifi".to_string()),
|
back: Some("/network/wifi".to_string()),
|
||||||
flash_name: None,
|
flash_name: None,
|
||||||
flash_msg: None,
|
flash_msg: None,
|
||||||
selected: Some(decoded_ssid.to_string()),
|
selected: Some(ssid.to_string()),
|
||||||
title: Some("Update WiFi Password".to_string()),
|
title: Some("Update WiFi Password".to_string()),
|
||||||
};
|
};
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("network_modify", &context)
|
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> {
|
pub fn wifi_set_password(wifi: Form<WiFi>) -> Flash<Redirect> {
|
||||||
let ssid = &wifi.ssid;
|
let ssid = &wifi.ssid;
|
||||||
let pass = &wifi.pass;
|
let pass = &wifi.pass;
|
||||||
let url = uri!(network_detail: ssid);
|
let url = uri!(network_detail(ssid = ssid));
|
||||||
match network_client::update("wlan0", ssid, pass) {
|
match network_client::update("wlan0", ssid, pass) {
|
||||||
Ok(_) => Flash::success(Redirect::to(url), "WiFi password updated".to_string()),
|
Ok(_) => Flash::success(Redirect::to(url), "WiFi password updated".to_string()),
|
||||||
Err(_) => Flash::error(
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("network_card", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("network_list", &context)
|
Template::render("network_list", &context)
|
||||||
@ -540,19 +540,17 @@ impl NetworkDetailContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/network/wifi?<ssid>")]
|
#[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
|
// assign context through context_builder call
|
||||||
let mut context = NetworkDetailContext::build();
|
let mut context = NetworkDetailContext::build();
|
||||||
context.back = Some("/network/wifi".to_string());
|
context.back = Some("/network/wifi".to_string());
|
||||||
context.title = Some("WiFi Network".to_string());
|
context.title = Some("WiFi Network".to_string());
|
||||||
// decode ssid from url
|
context.selected = Some(ssid.to_string());
|
||||||
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
|
|
||||||
context.selected = Some(decoded_ssid.to_string());
|
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("network_detail", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("network_add", &context)
|
Template::render("network_add", &context)
|
||||||
@ -611,18 +609,16 @@ impl NetworkAddContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/network/wifi/add?<ssid>")]
|
#[get("/network/wifi/add?<ssid>")]
|
||||||
pub fn network_add_ssid(ssid: &RawStr, flash: Option<FlashMessage>) -> Template {
|
pub fn network_add_ssid(ssid: &str, flash: Option<FlashMessage>) -> Template {
|
||||||
// decode ssid from url
|
|
||||||
let decoded_ssid = percent_decode(ssid.as_bytes()).decode_utf8().unwrap();
|
|
||||||
let mut context = NetworkAddContext::build();
|
let mut context = NetworkAddContext::build();
|
||||||
context.back = Some("/network/wifi".to_string());
|
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
glyph
commented
Was this commented-out line left in by mistake? Was this commented-out line left in by mistake?
notplants
commented
yup this was a mistake, good catch yup this was a mistake, good catch
|
|||||||
context.title = Some("Add WiFi Network".to_string());
|
context.title = Some("Add WiFi Network".to_string());
|
||||||
// check to see if there is a flash message to display
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("network_add", &context)
|
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
|
// check to see if there is a flash message to display
|
||||||
if let Some(flash) = flash {
|
if let Some(flash) = flash {
|
||||||
// add flash message contents to the context object
|
// add flash message contents to the context object
|
||||||
context.flash_name = Some(flash.name().to_string());
|
context.flash_name = Some(flash.kind().to_string());
|
||||||
context.flash_msg = Some(flash.msg().to_string());
|
context.flash_msg = Some(flash.message().to_string());
|
||||||
};
|
};
|
||||||
// template_dir is set in Rocket.toml
|
// template_dir is set in Rocket.toml
|
||||||
Template::render("network_usage", &context)
|
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>")]
|
#[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()) {
|
match monitor::update_store(thresholds.into_inner()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("WiFi data usage thresholds updated.");
|
debug!("WiFi data usage thresholds updated.");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Updated alert threshold and flags.".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(_) => {
|
Err(_) => {
|
||||||
warn!("Failed to update WiFi data usage thresholds.");
|
warn!("Failed to update WiFi data usage thresholds.");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to update WiFi data usage thresholds.".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")]
|
#[post("/api/v1/network/wifi/usage/reset")]
|
||||||
pub fn reset_data_total() -> Json<JsonResponse> {
|
pub fn reset_data_total() -> Value {
|
||||||
match monitor::reset_data() {
|
match monitor::reset_data() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Reset network data usage total.");
|
debug!("Reset network data usage total.");
|
||||||
@ -795,13 +791,13 @@ pub fn reset_data_total() -> Json<JsonResponse> {
|
|||||||
let data = json!(current_traffic);
|
let data = json!(current_traffic);
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Reset network data usage total.".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(_) => {
|
Err(_) => {
|
||||||
warn!("Failed to reset network data usage total.");
|
warn!("Failed to reset network data usage total.");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to reset network data usage total.".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
|
// HELPERS AND ROUTES FOR ACCESS POINT ACTIVATION
|
||||||
|
|
||||||
#[post("/api/v1/network/activate_ap")]
|
#[post("/api/v1/network/activate_ap")]
|
||||||
pub fn activate_ap() -> Json<JsonResponse> {
|
pub fn activate_ap() -> Value {
|
||||||
// activate the wireless access point
|
// activate the wireless access point
|
||||||
debug!("Activating WiFi access point.");
|
debug!("Activating WiFi access point.");
|
||||||
match network_client::activate_ap() {
|
match network_client::activate_ap() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
Json(build_json_response(status, None, None))
|
build_json_response(status, None, None)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to activate WiFi access point.".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
|
// HELPERS AND ROUTES FOR WIFI CLIENT MANAGEMENT
|
||||||
|
|
||||||
#[post("/api/v1/network/activate_client")]
|
#[post("/api/v1/network/activate_client")]
|
||||||
pub fn activate_client() -> Json<JsonResponse> {
|
pub fn activate_client() -> Value {
|
||||||
// activate the wireless client
|
// activate the wireless client
|
||||||
debug!("Activating WiFi client mode.");
|
debug!("Activating WiFi client mode.");
|
||||||
match network_client::activate_client() {
|
match network_client::activate_client() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
Json(build_json_response(status, None, None))
|
build_json_response(status, None, None)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to activate WiFi client mode.".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>")]
|
#[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
|
// generate and write wifi config to wpa_supplicant
|
||||||
match network_client::add(&wifi.ssid, &wifi.pass) {
|
match network_client::add(&wifi.ssid, &wifi.pass) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@ -858,20 +854,20 @@ pub fn add_wifi(wifi: Json<WiFi>) -> Json<JsonResponse> {
|
|||||||
// json response for successful update
|
// json response for successful update
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "WiFi credentials added.".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(_) => {
|
Err(_) => {
|
||||||
debug!("Failed to add WiFi credentials.");
|
debug!("Failed to add WiFi credentials.");
|
||||||
// json response for failed update
|
// json response for failed update
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to add WiFi credentials.".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>")]
|
#[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
|
// retrieve the id for the given network ssid
|
||||||
match network_client::id("wlan0", &ssid.ssid) {
|
match network_client::id("wlan0", &ssid.ssid) {
|
||||||
// attempt connection with the given network
|
// attempt connection with the given network
|
||||||
@ -879,60 +875,60 @@ pub fn connect_ap(ssid: Json<Ssid>) -> Json<JsonResponse> {
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Connected to chosen network.".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(_) => {
|
Err(_) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to connect to chosen network.".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(_) => {
|
Err(_) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to retrieve the network ID.".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>")]
|
#[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
|
// attempt to disable the current network for wlan0 interface
|
||||||
match network_client::disable("wlan0", &ssid.ssid) {
|
match network_client::disable("wlan0", &ssid.ssid) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Disconnected from WiFi network.".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(_) => {
|
Err(_) => {
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to disconnect from WiFi network.".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>")]
|
#[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;
|
let ssid = &network.ssid;
|
||||||
match network_client::forget("wlan0", ssid) {
|
match network_client::forget("wlan0", ssid) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Removed WiFi credentials for chosen network.");
|
debug!("Removed WiFi credentials for chosen network.");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "WiFi network credentials removed.".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(_) => {
|
Err(_) => {
|
||||||
warn!("Failed to remove WiFi credentials.");
|
warn!("Failed to remove WiFi credentials.");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to remove WiFi network credentials.".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>")]
|
#[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 ssid = &wifi.ssid;
|
||||||
let pass = &wifi.pass;
|
let pass = &wifi.pass;
|
||||||
// we are using a helper function (`update`) to delete the old
|
// 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.");
|
debug!("WiFi password updated for chosen network.");
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "WiFi password updated.".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(_) => {
|
Err(_) => {
|
||||||
warn!("Failed to update WiFi password.");
|
warn!("Failed to update WiFi password.");
|
||||||
let status = "error".to_string();
|
let status = "error".to_string();
|
||||||
let msg = "Failed to update WiFi password.".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
|
// HELPERS AND ROUTES FOR NETWORK STATE QUERIES
|
||||||
|
|
||||||
#[get("/api/v1/network/ip")]
|
#[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
|
// retrieve ip for wlan0 or set to x.x.x.x if not found
|
||||||
let wlan_ip = match network_client::ip("wlan0") {
|
let wlan_ip = match network_client::ip("wlan0") {
|
||||||
Ok(ip) => ip,
|
Ok(ip) => ip,
|
||||||
@ -973,45 +969,45 @@ pub fn return_ip() -> Json<JsonResponse> {
|
|||||||
"ap0": ap_ip
|
"ap0": ap_ip
|
||||||
});
|
});
|
||||||
let status = "success".to_string();
|
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")]
|
#[get("/api/v1/network/rssi")]
|
||||||
pub fn return_rssi() -> Json<JsonResponse> {
|
pub fn return_rssi() -> Value {
|
||||||
// retrieve rssi for connected network
|
// retrieve rssi for connected network
|
||||||
match network_client::rssi("wlan0") {
|
match network_client::rssi("wlan0") {
|
||||||
Ok(rssi) => {
|
Ok(rssi) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let data = json!(rssi);
|
let data = json!(rssi);
|
||||||
Json(build_json_response(status, Some(data), None))
|
build_json_response(status, Some(data), None)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Not currently connected to an access point.".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")]
|
#[get("/api/v1/network/ssid")]
|
||||||
pub fn return_ssid() -> Json<JsonResponse> {
|
pub fn return_ssid() -> Value {
|
||||||
// retrieve ssid for connected network
|
// retrieve ssid for connected network
|
||||||
match network_client::ssid("wlan0") {
|
match network_client::ssid("wlan0") {
|
||||||
Ok(network) => {
|
Ok(network) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let data = json!(network);
|
let data = json!(network);
|
||||||
Json(build_json_response(status, Some(data), None))
|
build_json_response(status, Some(data), None)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Not currently connected to an access point.".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")]
|
#[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
|
// retrieve state of wlan0 or set to x.x.x.x if not found
|
||||||
let wlan_state = match network_client::state("wlan0") {
|
let wlan_state = match network_client::state("wlan0") {
|
||||||
Ok(state) => state,
|
Ok(state) => state,
|
||||||
@ -1027,39 +1023,39 @@ pub fn return_state() -> Json<JsonResponse> {
|
|||||||
"ap0": ap_state
|
"ap0": ap_state
|
||||||
});
|
});
|
||||||
let status = "success".to_string();
|
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")]
|
#[get("/api/v1/network/status")]
|
||||||
pub fn return_status() -> Json<JsonResponse> {
|
pub fn return_status() -> Value {
|
||||||
// retrieve status info for wlan0 interface
|
// retrieve status info for wlan0 interface
|
||||||
match network_client::status("wlan0") {
|
match network_client::status("wlan0") {
|
||||||
Ok(network) => {
|
Ok(network) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let data = json!(network);
|
let data = json!(network);
|
||||||
Json(build_json_response(status, Some(data), None))
|
build_json_response(status, Some(data), None)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Not currently connected to an access point.".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")]
|
#[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
|
// retrieve scan results for access-points within range of wlan0
|
||||||
match network_client::available_networks("wlan0") {
|
match network_client::available_networks("wlan0") {
|
||||||
Ok(networks) => {
|
Ok(networks) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let data = json!(networks);
|
let data = json!(networks);
|
||||||
Json(build_json_response(status, Some(data), None))
|
build_json_response(status, Some(data), None)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let msg = "Unable to scan for networks. Interface may be deactivated.".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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use rocket::serde::json::{Value, json};
|
||||||
use rocket::http::{ContentType, Status};
|
use rocket::http::{ContentType, Status};
|
||||||
use rocket::local::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket_contrib::json;
|
|
||||||
|
|
||||||
use crate::utils::build_json_response;
|
use crate::utils::build_json_response;
|
||||||
|
|
||||||
use super::rocket;
|
use super::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::new(rocket()).unwrap();
|
let client = Client::tracked(init_rocket()).unwrap();
|
||||||
let mut response = client.get(path).dispatch();
|
let response = client.get(path).dispatch();
|
||||||
assert_eq!(response.status(), status);
|
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() {
|
if let Some(filename) = file.into() {
|
||||||
let expected_data = read_file_content(filename);
|
let expected_data = read_file_content(filename);
|
||||||
assert!(body_data.map_or(false, |s| s == expected_data));
|
assert!(body_data.map_or(false, |s| s == expected_data));
|
||||||
@ -39,11 +39,11 @@ fn read_file_content(path: &str) -> Vec<u8> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_html() {
|
fn index_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut 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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("/peers"));
|
assert!(body.contains("/peers"));
|
||||||
assert!(body.contains("/profile"));
|
assert!(body.contains("/profile"));
|
||||||
assert!(body.contains("/messages"));
|
assert!(body.contains("/messages"));
|
||||||
@ -54,11 +54,11 @@ fn index_html() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn network_card_html() {
|
fn network_card_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/network").dispatch();
|
let response = client.get("/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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("MODE"));
|
assert!(body.contains("MODE"));
|
||||||
assert!(body.contains("SSID"));
|
assert!(body.contains("SSID"));
|
||||||
assert!(body.contains("IP"));
|
assert!(body.contains("IP"));
|
||||||
@ -72,11 +72,11 @@ fn network_card_html() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn network_list_html() {
|
fn network_list_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/network/wifi").dispatch();
|
let response = client.get("/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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("WiFi Networks"));
|
assert!(body.contains("WiFi Networks"));
|
||||||
assert!(body.contains("No saved or available networks found."));
|
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
|
// TODO: needs further testing once template has been refactored
|
||||||
#[test]
|
#[test]
|
||||||
fn network_detail_html() {
|
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();
|
let response = client.get("/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));
|
||||||
//let body = response.body_string().unwrap();
|
//let body = response.into_string().unwrap();
|
||||||
//assert!(body.contains("Network not found"));
|
//assert!(body.contains("Network not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn network_add_html() {
|
fn network_add_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/network/wifi/add").dispatch();
|
let response = client.get("/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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Add WiFi Network"));
|
assert!(body.contains("Add WiFi Network"));
|
||||||
assert!(body.contains("SSID"));
|
assert!(body.contains("SSID"));
|
||||||
assert!(body.contains("Password"));
|
assert!(body.contains("Password"));
|
||||||
@ -108,11 +108,11 @@ fn network_add_html() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn network_add_ssid_html() {
|
fn network_add_ssid_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/network/wifi/add?ssid=Home").dispatch();
|
let response = client.get("/network/wifi/add?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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Add WiFi Network"));
|
assert!(body.contains("Add WiFi Network"));
|
||||||
assert!(body.contains("Home"));
|
assert!(body.contains("Home"));
|
||||||
assert!(body.contains("Password"));
|
assert!(body.contains("Password"));
|
||||||
@ -122,11 +122,11 @@ fn network_add_ssid_html() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn device_html() {
|
fn device_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/device").dispatch();
|
let response = client.get("/device").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.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Device Status"));
|
assert!(body.contains("Device Status"));
|
||||||
assert!(body.contains("Networking"));
|
assert!(body.contains("Networking"));
|
||||||
assert!(body.contains("Display"));
|
assert!(body.contains("Display"));
|
||||||
@ -135,71 +135,71 @@ fn device_html() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn help_html() {
|
fn help_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut 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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Help"));
|
assert!(body.contains("Help"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn login_html() {
|
fn login_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut 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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Login"));
|
assert!(body.contains("Login"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn messages_html() {
|
fn messages_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/messages").dispatch();
|
let response = client.get("/messages").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.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Private Messages"));
|
assert!(body.contains("Private Messages"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn peers_html() {
|
fn peers_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/peers").dispatch();
|
let response = client.get("/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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Scuttlebutt Peers"));
|
assert!(body.contains("Scuttlebutt Peers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn profile_html() {
|
fn profile_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/profile").dispatch();
|
let response = client.get("/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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Profile"));
|
assert!(body.contains("Profile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shutdown_html() {
|
fn shutdown_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/shutdown").dispatch();
|
let response = client.get("/shutdown").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.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Shutdown Device"));
|
assert!(body.contains("Shutdown Device"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn network_usage_html() {
|
fn network_usage_html() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client.get("/network/wifi/usage").dispatch();
|
let response = client.get("/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));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("Network Data Usage"));
|
assert!(body.contains("Network Data Usage"));
|
||||||
assert!(body.contains("WARNING THRESHOLD"));
|
assert!(body.contains("WARNING THRESHOLD"));
|
||||||
assert!(body.contains("Update"));
|
assert!(body.contains("Update"));
|
||||||
@ -208,7 +208,7 @@ fn network_usage_html() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_credentials() {
|
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
|
let response = client
|
||||||
.post("/network/wifi/add")
|
.post("/network/wifi/add")
|
||||||
.header(ContentType::Form)
|
.header(ContentType::Form)
|
||||||
@ -220,7 +220,7 @@ fn add_credentials() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forget_wifi() {
|
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
|
let response = client
|
||||||
.post("/network/wifi/forget")
|
.post("/network/wifi/forget")
|
||||||
.header(ContentType::Form)
|
.header(ContentType::Form)
|
||||||
@ -232,7 +232,7 @@ fn forget_wifi() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn modify_password() {
|
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
|
let response = client
|
||||||
.post("/network/wifi/modify")
|
.post("/network/wifi/modify")
|
||||||
.header(ContentType::Form)
|
.header(ContentType::Form)
|
||||||
@ -244,7 +244,7 @@ fn modify_password() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deploy_ap() {
|
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();
|
let response = client.get("/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);
|
||||||
@ -253,7 +253,7 @@ fn deploy_ap() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deploy_client() {
|
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();
|
let response = client.get("/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);
|
||||||
@ -264,7 +264,7 @@ fn deploy_client() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn activate_ap() {
|
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
|
let response = client
|
||||||
.post("/api/v1/network/activate_ap")
|
.post("/api/v1/network/activate_ap")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
@ -275,7 +275,7 @@ fn activate_ap() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn activate_client() {
|
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
|
let response = client
|
||||||
.post("/api/v1/network/activate_client")
|
.post("/api/v1/network/activate_client")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
@ -286,54 +286,54 @@ fn activate_client() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn return_ip() {
|
fn return_ip() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.get("/api/v1/network/ip")
|
.get("/api/v1/network/ip")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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("wlan0"));
|
||||||
assert!(body.contains("ap0"));
|
assert!(body.contains("ap0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn return_rssi() {
|
fn return_rssi() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.get("/api/v1/network/rssi")
|
.get("/api/v1/network/rssi")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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."));
|
assert!(body.contains("Not currently connected to an access point."));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn return_ssid() {
|
fn return_ssid() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.get("/api/v1/network/ssid")
|
.get("/api/v1/network/ssid")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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."));
|
assert!(body.contains("Not currently connected to an access point."));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn return_state() {
|
fn return_state() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.get("/api/v1/network/state")
|
.get("/api/v1/network/state")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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("ap0"));
|
||||||
assert!(body.contains("wlan0"));
|
assert!(body.contains("wlan0"));
|
||||||
assert!(body.contains("unavailable"));
|
assert!(body.contains("unavailable"));
|
||||||
@ -341,82 +341,82 @@ fn return_state() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn return_status() {
|
fn return_status() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.get("/api/v1/network/status")
|
.get("/api/v1/network/status")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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."));
|
assert!(body.contains("Not currently connected to an access point."));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_networks() {
|
fn scan_networks() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.get("/api/v1/network/wifi")
|
.get("/api/v1/network/wifi")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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."));
|
assert!(body.contains("Unable to scan for networks. Interface may be deactivated."));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_wifi() {
|
fn add_wifi() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.post("/api/v1/network/wifi")
|
.post("/api/v1/network/wifi")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.body(r#"{ "ssid": "Home", "pass": "Password" }"#)
|
.body(r#"{ "ssid": "Home", "pass": "Password" }"#)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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."));
|
assert!(body.contains("Failed to add WiFi credentials."));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_wifi() {
|
fn remove_wifi() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.post("/api/v1/network/wifi/forget")
|
.post("/api/v1/network/wifi/forget")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.body(r#"{ "ssid": "Home" }"#)
|
.body(r#"{ "ssid": "Home" }"#)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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."));
|
assert!(body.contains("Failed to remove WiFi network credentials."));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_password() {
|
fn new_password() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.post("/api/v1/network/wifi/modify")
|
.post("/api/v1/network/wifi/modify")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.body(r#"{ "ssid": "Home", "pass": "Password" }"#)
|
.body(r#"{ "ssid": "Home", "pass": "Password" }"#)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
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."));
|
assert!(body.contains("Failed to update WiFi password."));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ping_pong() {
|
fn ping_pong() {
|
||||||
let client = Client::new(rocket()).expect("valid rocket instance");
|
let client = Client::tracked(init_rocket()).expect("valid rocket instance");
|
||||||
let mut response = client
|
let response = client
|
||||||
.get("/api/v1/ping")
|
.get("/api/v1/ping")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
||||||
let body = response.body_string().unwrap();
|
let body = response.into_string().unwrap();
|
||||||
assert!(body.contains("pong!"));
|
assert!(body.contains("pong!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,12 +426,13 @@ fn ping_pong() {
|
|||||||
fn test_build_json_response() {
|
fn test_build_json_response() {
|
||||||
let status = "success".to_string();
|
let status = "success".to_string();
|
||||||
let data = json!("WiFi credentials added.".to_string());
|
let data = json!("WiFi credentials added.".to_string());
|
||||||
let json = build_json_response(status, Some(data), None);
|
let j: Value = build_json_response(status, Some(data), None);
|
||||||
assert_eq!(json.status, "success");
|
assert_eq!(j["status"], "success");
|
||||||
assert_eq!(json.data, Some(json!("WiFi credentials added.")));
|
assert_eq!(j["data"], "WiFi credentials added.");
|
||||||
assert_eq!(json.msg, None);
|
assert_eq!(j["msg"], json!(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// FILE TESTS
|
// FILE TESTS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -471,16 +472,16 @@ fn invalid_path() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_get_request() {
|
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
|
// try to get a path that doesn't exist
|
||||||
let mut res = client
|
let res = client
|
||||||
.get("/message/99")
|
.get("/message/99")
|
||||||
.header(ContentType::JSON)
|
.header(ContentType::JSON)
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(res.status(), Status::NotFound);
|
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("404: Page Not Found"));
|
||||||
assert!(body.contains("No PeachCloud resource exists for this URL."));
|
assert!(body.contains("No PeachCloud resource exists for this URL."));
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,16 @@
|
|||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::{Value, json};
|
||||||
use serde::Serialize;
|
use rocket::serde::{Serialize};
|
||||||
|
|
||||||
// HELPER FUNCTIONS
|
// 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(
|
pub fn build_json_response(
|
||||||
status: String,
|
status: String,
|
||||||
data: Option<JsonValue>,
|
data: Option<Value>,
|
||||||
msg: Option<String>,
|
msg: Option<String>,
|
||||||
) -> JsonResponse {
|
) -> Value {
|
||||||
JsonResponse { status, data, msg }
|
json!({ "status": status, "data": data, "msg": msg })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use nest::{Error, Store, Value};
|
use nest::{Error, Store, Value};
|
||||||
use rocket::request::FromForm;
|
use rocket::form::{FromForm};
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
/// Network traffic data total
|
/// Network traffic data total
|
||||||
|
@ -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(())
|
|
||||||
}
|
|
The new
FileServer
API / workflow is really nice.