Merge pull request 'Remove json routes, utils and javascript' (#65) from remove_json_js into main

Reviewed-on: #65
This commit is contained in:
glyph 2022-01-12 15:33:20 +00:00
commit eb77290a93
30 changed files with 14 additions and 1404 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "peach-web"
version = "0.4.17"
version = "0.5.0"
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018"
description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins."

View File

@ -4,11 +4,11 @@
## Web Interface for PeachCloud
**peach-web** provides a web interface for the PeachCloud device. It serves static assets and exposes a JSON API for programmatic interactions.
**peach-web** provides a web interface for the PeachCloud device.
Initial development is focused on administration of the device itself, beginning with networking functionality, with SSB-related administration to be integrated at a later stage.
The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web framework), [Tera](http://tera.netlify.com/) (Rust template engine), HTML, CSS and JavaScript.
The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web framework), [Tera](http://tera.netlify.com/) (Rust template engine), HTML and CSS.
_Note: This is a work-in-progress._
@ -95,13 +95,13 @@ Remove configuration files (not removed with `apt-get remove`):
### Design
`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. 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 plain 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.
`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call `peach-` libraries and serve HTML and assets. 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.
### Configuration
Configuration variables are stored in /var/lib/peachcloud/config.yml.
Peach-web also updates this file when changes are made to configurations via
the web interface. peach-web has no database, so all configurations are stored in this file.
the web interface. peach-web has no database, so all configurations are stored in this file.
#### Dynamic DNS Configuration

View File

@ -42,7 +42,6 @@ use crate::routes::index::*;
use crate::routes::scuttlebutt::*;
use crate::routes::status::device::*;
use crate::routes::status::network::*;
use crate::routes::status::ping::*;
use crate::routes::settings::admin::*;
use crate::routes::settings::dns::*;
@ -123,43 +122,6 @@ fn init_rocket() -> Rocket<Build> {
block, publish,
],
)
// GENERAL JSON API ROUTES
.mount(
"/api/v1",
routes![ping_pong, ping_network, ping_oled, ping_stats,],
)
// ADMIN JSON API ROUTES
.mount(
"/api/v1/admin",
routes![
save_password_form_endpoint,
reset_password_form_endpoint,
reboot_device,
shutdown_device,
],
)
// NETWORK JSON API ROUTES
.mount(
"/api/v1/network",
routes![
activate_ap,
activate_client,
add_wifi_credentials,
connect_ap,
disconnect_ap,
forget_ap,
modify_password,
reset_data_total,
return_ip,
return_rssi,
return_ssid,
return_state,
return_status,
scan_networks,
update_wifi_alerts,
save_dns_configuration_endpoint,
],
)
.mount("/", FileServer::from("static"))
.register("/", catchers![not_found, internal_error, forbidden])
.attach(Template::fairing())

View File

@ -3,10 +3,7 @@ use rocket::form::{Form, FromForm};
use rocket::http::{Cookie, CookieJar, Status};
use rocket::request::{self, FlashMessage, FromRequest, Request};
use rocket::response::{Flash, Redirect};
use rocket::serde::{
json::{Json, Value},
Deserialize, Serialize,
};
use rocket::serde::{Deserialize, Serialize};
use rocket::{get, post, Config};
use rocket_dyn_templates::Template;
@ -14,7 +11,7 @@ use peach_lib::error::PeachError;
use peach_lib::password_utils;
use crate::error::PeachWebError;
use crate::utils::{build_json_response, TemplateOrRedirect};
use crate::utils::TemplateOrRedirect;
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
@ -260,25 +257,6 @@ pub fn reset_password_post(reset_password_form: Form<ResetPasswordForm>) -> Temp
}
}
/// JSON password reset form request handler. This route is used by a user who is not logged in
/// and is specifically for users who have forgotten their password.
#[post("/reset_password", data = "<reset_password_form>")]
pub fn reset_password_form_endpoint(reset_password_form: Json<ResetPasswordForm>) -> Value {
let result = save_reset_password_form(reset_password_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "New password is now saved. Return home to login.".to_string();
build_json_response(status, None, Some(msg))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
build_json_response(status, None, Some(msg))
}
}
}
// HELPERS AND ROUTES FOR /send_password_reset
#[derive(Debug, Serialize)]
@ -414,24 +392,3 @@ pub fn change_password_post(password_form: Form<PasswordForm>, _auth: Authentica
}
}
}
/// JSON change password form request handler.
#[post("/change_password", data = "<password_form>")]
pub fn save_password_form_endpoint(
password_form: Json<PasswordForm>,
_auth: Authenticated,
) -> Value {
let result = save_password_form(password_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "Your password was successfully changed".to_string();
build_json_response(status, None, Some(msg))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -3,10 +3,7 @@ use rocket::{
form::{Form, FromForm},
get, post,
request::FlashMessage,
serde::{
json::{Json, Value},
Deserialize, Serialize,
},
serde::{Deserialize, Serialize},
};
use rocket_dyn_templates::Template;
@ -23,7 +20,6 @@ use peach_lib::jsonrpc_core::types::error::ErrorCode;
use crate::error::PeachWebError;
use crate::routes::authentication::Authenticated;
use crate::utils::build_json_response;
#[derive(Debug, Deserialize, FromForm)]
pub struct DnsForm {
@ -153,20 +149,3 @@ pub fn configure_dns_post(dns: Form<DnsForm>, _auth: Authenticated) -> Template
}
}
}
#[post("/dns/configure", data = "<dns_form>")]
pub fn save_dns_configuration_endpoint(dns_form: Json<DnsForm>, _auth: Authenticated) -> Value {
let result = save_dns_configuration(dns_form.into_inner());
match result {
Ok(_) => {
let status = "success".to_string();
let msg = "New dynamic dns configuration is now enabled".to_string();
build_json_response(status, None, Some(msg))
}
Err(err) => {
let status = "error".to_string();
let msg = format!("{}", err);
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -5,10 +5,7 @@ use rocket::{
get, post,
request::FlashMessage,
response::{Flash, Redirect},
serde::{
json::{json, Json, Value},
Deserialize, Serialize,
},
serde::{Deserialize, Serialize},
uri, UriDisplayQuery,
};
use rocket_dyn_templates::Template;
@ -19,7 +16,6 @@ use peach_lib::network_client::{AccessPoint, Networks, Scan};
use peach_lib::stats_client::Traffic;
use crate::routes::authentication::Authenticated;
use crate::utils::build_json_response;
use crate::utils::monitor;
use crate::utils::monitor::{Alert, Data, Threshold};
@ -760,311 +756,3 @@ pub fn wifi_usage_alerts(thresholds: Form<Threshold>, _auth: Authenticated) -> F
}
}
}
// JSON ROUTES FOR NETWORK SETTINGS
#[post("/wifi/usage", data = "<thresholds>")]
pub fn update_wifi_alerts(thresholds: Json<Threshold>, _auth: Authenticated) -> Value {
match monitor::update_store(thresholds.into_inner()) {
Ok(_) => {
debug!("WiFi data usage thresholds updated.");
let status = "success".to_string();
let msg = "Updated alert threshold and flags.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Failed to update WiFi data usage thresholds.");
let status = "error".to_string();
let msg = "Failed to update WiFi data usage thresholds.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[post("/wifi/usage/reset")]
pub fn reset_data_total(_auth: Authenticated) -> Value {
match monitor::reset_data() {
Ok(_) => {
debug!("Reset network data usage total.");
let traffic = match network_client::traffic("wlan0") {
Ok(t) => t,
Err(_) => Traffic {
received: 0,
transmitted: 0,
rx_unit: None,
tx_unit: None,
},
};
// current wifi traffic values as bytes
let current_traffic = traffic.received + traffic.transmitted;
let data = json!(current_traffic);
let status = "success".to_string();
let msg = "Reset network data usage total.".to_string();
build_json_response(status, Some(data), Some(msg))
}
Err(_) => {
warn!("Failed to reset network data usage total.");
let status = "error".to_string();
let msg = "Failed to reset network data usage total.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
// HELPERS AND ROUTES FOR ACCESS POINT ACTIVATION
#[post("/activate_ap")]
pub fn activate_ap(_auth: Authenticated) -> Value {
// activate the wireless access point
debug!("Activating WiFi access point.");
match network_client::activate_ap() {
Ok(_) => {
let status = "success".to_string();
build_json_response(status, None, None)
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to activate WiFi access point.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
// HELPERS AND ROUTES FOR WIFI CLIENT MANAGEMENT
#[post("/activate_client")]
pub fn activate_client(_auth: Authenticated) -> Value {
// activate the wireless client
debug!("Activating WiFi client mode.");
match network_client::activate_client() {
Ok(_) => {
let status = "success".to_string();
build_json_response(status, None, None)
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to activate WiFi client mode.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[post("/wifi", data = "<wifi>")]
pub fn add_wifi_credentials(wifi: Json<WiFi>, _auth: Authenticated) -> Value {
// generate and write wifi config to wpa_supplicant
match network_client::add(&wifi.ssid, &wifi.pass) {
Ok(_) => {
debug!("Added WiFi credentials.");
// force reread of wpa_supplicant.conf file with new credentials
match network_client::reconfigure() {
Ok(_) => debug!("Successfully reconfigured wpa_supplicant."),
Err(_) => warn!("Failed to reconfigure wpa_supplicant."),
}
// json response for successful update
let status = "success".to_string();
let msg = "WiFi credentials added.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
debug!("Failed to add WiFi credentials.");
// json response for failed update
let status = "error".to_string();
let msg = "Failed to add WiFi credentials.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[post("/wifi/connect", data = "<ssid>")]
pub fn connect_ap(ssid: Json<Ssid>, _auth: Authenticated) -> Value {
// retrieve the id for the given network ssid
match network_client::id("wlan0", &ssid.ssid) {
// attempt connection with the given network
Ok(id) => match network_client::connect(&id, "wlan0") {
Ok(_) => {
let status = "success".to_string();
let msg = "Connected to chosen network.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to connect to chosen network.".to_string();
build_json_response(status, None, Some(msg))
}
},
Err(_) => {
let status = "error".to_string();
let msg = "Failed to retrieve the network ID.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[post("/wifi/disconnect", data = "<ssid>")]
pub fn disconnect_ap(ssid: Json<Ssid>, _auth: Authenticated) -> Value {
// attempt to disable the current network for wlan0 interface
match network_client::disable("wlan0", &ssid.ssid) {
Ok(_) => {
let status = "success".to_string();
let msg = "Disconnected from WiFi network.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
let status = "error".to_string();
let msg = "Failed to disconnect from WiFi network.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[post("/wifi/forget", data = "<network>")]
pub fn forget_ap(network: Json<Ssid>, _auth: Authenticated) -> Value {
let ssid = &network.ssid;
match network_client::forget("wlan0", ssid) {
Ok(_) => {
debug!("Removed WiFi credentials for chosen network.");
let status = "success".to_string();
let msg = "WiFi network credentials removed.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Failed to remove WiFi credentials.");
let status = "error".to_string();
let msg = "Failed to remove WiFi network credentials.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[post("/wifi/modify", data = "<wifi>")]
pub fn modify_password(wifi: Json<WiFi>, _auth: Authenticated) -> Value {
let ssid = &wifi.ssid;
let pass = &wifi.pass;
// we are using a helper function (`update`) to delete the old
// credentials and add the new ones. this is because the wpa_cli method
// for updating the password does not work.
match network_client::update("wlan0", ssid, pass) {
Ok(_) => {
debug!("WiFi password updated for chosen network.");
let status = "success".to_string();
let msg = "WiFi password updated.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Failed to update WiFi password.");
let status = "error".to_string();
let msg = "Failed to update WiFi password.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
// HELPERS AND ROUTES FOR NETWORK STATE QUERIES
#[get("/ip")]
pub fn return_ip(_auth: Authenticated) -> Value {
// retrieve ip for wlan0 or set to x.x.x.x if not found
let wlan_ip = match network_client::ip("wlan0") {
Ok(ip) => ip,
Err(_) => "x.x.x.x".to_string(),
};
// retrieve ip for ap0 or set to x.x.x.x if not found
let ap_ip = match network_client::ip("ap0") {
Ok(ip) => ip,
Err(_) => "x.x.x.x".to_string(),
};
let data = json!({
"wlan0": wlan_ip,
"ap0": ap_ip
});
let status = "success".to_string();
build_json_response(status, Some(data), None)
}
#[get("/rssi")]
pub fn return_rssi(_auth: Authenticated) -> Value {
// retrieve rssi for connected network
match network_client::rssi("wlan0") {
Ok(rssi) => {
let status = "success".to_string();
let data = json!(rssi);
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[get("/ssid")]
pub fn return_ssid(_auth: Authenticated) -> Value {
// retrieve ssid for connected network
match network_client::ssid("wlan0") {
Ok(network) => {
let status = "success".to_string();
let data = json!(network);
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[get("/state")]
pub fn return_state(_auth: Authenticated) -> Value {
// retrieve state of wlan0 or set to x.x.x.x if not found
let wlan_state = match network_client::state("wlan0") {
Ok(state) => state,
Err(_) => "unavailable".to_string(),
};
// retrieve state for ap0 or set to x.x.x.x if not found
let ap_state = match network_client::state("ap0") {
Ok(state) => state,
Err(_) => "unavailable".to_string(),
};
let data = json!({
"wlan0": wlan_state,
"ap0": ap_state
});
let status = "success".to_string();
build_json_response(status, Some(data), None)
}
#[get("/status")]
pub fn return_status(_auth: Authenticated) -> Value {
// retrieve status info for wlan0 interface
match network_client::status("wlan0") {
Ok(network) => {
let status = "success".to_string();
let data = json!(network);
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Not currently connected to an access point.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
#[get("/wifi")]
pub fn scan_networks(_auth: Authenticated) -> Value {
// retrieve scan results for access-points within range of wlan0
match network_client::available_networks("wlan0") {
Ok(networks) => {
let status = "success".to_string();
let data = json!(networks);
build_json_response(status, Some(data), None)
}
Err(_) => {
let status = "success".to_string();
let msg = "Unable to scan for networks. Interface may be deactivated.".to_string();
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -1,9 +1,8 @@
use log::{debug, info, warn};
use log::info;
use rocket::{
get, post,
get,
request::FlashMessage,
response::{Flash, Redirect},
serde::json::Value,
};
use rocket_dyn_templates::Template;
use serde::Serialize;
@ -21,7 +20,6 @@ use peach_stats::{
};
use crate::routes::authentication::Authenticated;
use crate::utils::build_json_response;
// HELPERS AND ROUTES FOR /status
@ -189,25 +187,6 @@ pub fn reboot_cmd(_auth: Authenticated) -> Flash<Redirect> {
}
}
/// JSON request handler for device reboot.
#[post("/api/v1/admin/reboot")]
pub fn reboot_device(_auth: Authenticated) -> Value {
match reboot() {
Ok(_) => {
debug!("Going down for reboot...");
let status = "success".to_string();
let msg = "Going down for reboot.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Reboot failed");
let status = "error".to_string();
let msg = "Failed to reboot the device.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
// HELPERS AND ROUTES FOR /power/shutdown
/// Executes a system command to shutdown the device immediately.
@ -227,25 +206,6 @@ pub fn shutdown_cmd(_auth: Authenticated) -> Flash<Redirect> {
}
}
// shutdown the device
#[post("/power/shutdown")]
pub fn shutdown_device(_auth: Authenticated) -> Value {
match shutdown() {
Ok(_) => {
debug!("Going down for shutdown...");
let status = "success".to_string();
let msg = "Going down for shutdown.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("Shutdown failed");
let status = "error".to_string();
let msg = "Failed to shutdown the device.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
// HELPERS AND ROUTES FOR /power
#[derive(Debug, Serialize)]

View File

@ -1,3 +1,2 @@
pub mod device;
pub mod network;
pub mod ping;

View File

@ -1,78 +0,0 @@
//! Helper routes for pinging services to check that they are active
use log::{debug, warn};
use rocket::get;
use rocket::serde::json::Value;
use peach_lib::network_client;
use peach_lib::oled_client;
use peach_lib::stats_client;
use crate::routes::authentication::Authenticated;
use crate::utils::build_json_response;
/// Status route: useful for checking connectivity from web client.
#[get("/ping")]
pub fn ping_pong(_auth: Authenticated) -> Value {
//pub fn ping_pong() -> Value {
// ping pong
let status = "success".to_string();
let msg = "pong!".to_string();
build_json_response(status, None, Some(msg))
}
/// Status route: check availability of `peach-network` microservice.
#[get("/ping/network")]
pub fn ping_network(_auth: Authenticated) -> Value {
match network_client::ping() {
Ok(_) => {
debug!("peach-network responded successfully");
let status = "success".to_string();
let msg = "peach-network is available.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("peach-network failed to respond");
let status = "error".to_string();
let msg = "peach-network is unavailable.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
/// Status route: check availability of `peach-oled` microservice.
#[get("/ping/oled")]
pub fn ping_oled(_auth: Authenticated) -> Value {
match oled_client::ping() {
Ok(_) => {
debug!("peach-oled responded successfully");
let status = "success".to_string();
let msg = "peach-oled is available.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("peach-oled failed to respond");
let status = "error".to_string();
let msg = "peach-oled is unavailable.".to_string();
build_json_response(status, None, Some(msg))
}
}
}
/// Status route: check availability of `peach-stats` microservice.
#[get("/ping/stats")]
pub fn ping_stats(_auth: Authenticated) -> Value {
match stats_client::ping() {
Ok(_) => {
debug!("peach-stats responded successfully");
let status = "success".to_string();
let msg = "peach-stats is available.".to_string();
build_json_response(status, None, Some(msg))
}
Err(_) => {
warn!("peach-stats failed to respond");
let status = "error".to_string();
let msg = "peach-stats is unavailable.".to_string();
build_json_response(status, None, Some(msg))
}
}
}

View File

@ -3,26 +3,16 @@ pub mod monitor;
use rocket_dyn_templates::Template;
use rocket::response::{Redirect, Responder};
use rocket::serde::json::{Value, json};
use rocket::serde::{Serialize};
use rocket::serde::Serialize;
// HELPER FUNCTIONS
pub fn build_json_response(
status: String,
data: Option<Value>,
msg: Option<String>,
) -> Value {
json!({ "status": status, "data": data, "msg": msg })
}
#[derive(Debug, Serialize)]
pub struct FlashContext {
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
}
/// A helper enum which allows routes to either return a Template or a Redirect
/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066
#[allow(clippy::large_enum_variant)]
@ -30,4 +20,4 @@ pub struct FlashContext {
pub enum TemplateOrRedirect {
Template(Template),
Redirect(Redirect),
}
}

View File

@ -1,57 +0,0 @@
/*
behavioural layer for the `change_password.html.tera` template
- intercept button click for save (form submission of passwords)
- perform json api call
- update the dom
methods:
PEACH_AUTH.changePassword();
*/
var PEACH_AUTH = {};
// catch click of 'Save' button and make POST request
PEACH_AUTH.changePassword = function() {
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior)
e.preventDefault();
// capture form data
var formElement = document.querySelector("form");
// create form data object from the changePassword form element
var formData = new FormData(formElement);
var object = {};
// assign values from form
formData.forEach(function(value, key){
object[key] = value;
});
// perform json serialization
console.log(object);
var jsonData = JSON.stringify(object);
// write in-progress status message to ui
PEACH.flashMsg("info", "Saving new password.");
// send change_password POST request
fetch("/api/v1/admin/change_password", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
});
}
var changePassInstance = PEACH_AUTH;
changePassInstance.changePassword();

View File

@ -1,46 +0,0 @@
/*
*
* Common javascript functions shared by multiple pages:
* - flashMsg
* - logout
*
*/
var PEACH = {};
// display a message by appending a paragraph element
PEACH.flashMsg = function(status, msg) {
// set the class of the element according to status
var elementClass;
if (status === "success") {
elementClass = "capsule center-text flash-message font-success";
} else if (status === "info") {
elementClass = "capsule center-text flash-message font-info";
} else {
elementClass = "capsule center-text flash-message font-failure";
};
var flashElement = document.getElementById("flashMsg");
// if flashElement exists, update the class & text
if (flashElement) {
flashElement.className = elementClass;
flashElement.innerText = msg;
// if flashElement does not exist, create it, set id, class, text & append
} else {
// create new div for flash message
var flashDiv = document.createElement("DIV");
// set div attributes
flashDiv.id = "flashMsg";
flashDiv.className = elementClass;
// add json response message to flash message div
var flashMsg = document.createTextNode(msg);
flashDiv.appendChild(flashMsg);
// insert the flash message div below the button div
var buttonDiv = document.getElementById("buttonDiv");
// flashDiv will be added to the end since buttonDiv is the last
// child within the parent element (card-container div)
buttonDiv.parentNode.insertBefore(flashDiv, buttonDiv.nextSibling);
}
}
var commonInstance = PEACH;

View File

@ -1,63 +0,0 @@
/*
behavioural layer for the `configure_dns.html.tera` template,
corresponding to the web route `/settings/network/dns`
- intercept button click for save (form submission of dns settings)
- perform json api call
- update the dom
*/
var PEACH_DNS = {};
// catch click of 'Add' button and make POST request
PEACH_DNS.configureDns = function() {
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior)
e.preventDefault();
// capture form data
var formElement = document.querySelector("form");
// create form data object from the configureDNS form element
var formData = new FormData(formElement);
var object = {};
// set checkbox to false (the value is only passed to formData if it is "on")
object["enable_dyndns"] = false;
// assign values from form
formData.forEach(function(value, key){
// convert checkbox to bool
if (key === "enable_dyndns") {
value = (value === "on");
}
object[key] = value;
});
// perform json serialization
console.log(object);
var jsonData = JSON.stringify(object);
// write in-progress status message to ui
PEACH.flashMsg("info", "Saving new DNS configurations");
// send add_wifi POST request
fetch("/api/v1/network/dns/configure", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
let statusIndicator = document.getElementById("dyndns-status-indicator");
// only remove the "dyndns-status-indicator" element if it exists
if (statusIndicator != null ) statusIndicator.remove();
})
}, false);
});
}
var configureDnsInstance = PEACH_DNS;
configureDnsInstance.configureDns();

View File

@ -1,57 +0,0 @@
/*
behavioural layer for the `network_add.html.tera` template,
corresponding to the web route `/network/wifi/add`
- intercept button click for add (form submission of credentials)
- perform json api call
- update the dom
methods:
PEACH_NETWORK.add();
*/
var PEACH_NETWORK = {};
// catch click of 'Add' button and make POST request
PEACH_NETWORK.add = function() {
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior)
e.preventDefault();
// capture form data
var formElement = document.querySelector("form");
// create form data object from the wifiCreds form element
var formData = new FormData(formElement);
var object = {};
// assign ssid and pass from form
formData.forEach(function(value, key){
object[key] = value;
});
// perform json serialization
var jsonData = JSON.stringify(object);
// write in-progress status message to ui
PEACH.flashMsg("info", "Adding WiFi credentials...");
// send add_wifi POST request
fetch("/api/v1/network/wifi", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
});
}
var addInstance = PEACH_NETWORK;
addInstance.add();

View File

@ -1,133 +0,0 @@
/*
behavioural layer for the `network_card.html.tera` template,
corresponding to the web route `/settings/network`
- intercept form submissions
- perform json api calls
- update the dom
methods:
PEACH_NETWORK.activateAp();
PEACH_NETWORK.activateClient();
PEACH_NETWORK.apMode();
PEACH_NETWORK.clientMode();
*/
var PEACH_NETWORK = {};
// catch click of 'Deploy Access Point' and make POST request
PEACH_NETWORK.activateAp = function() {
document.addEventListener('DOMContentLoaded', function() {
var deployAP = document.getElementById('deployAccessPoint');
if (deployAP) {
deployAP.addEventListener('click', function(e) {
// prevent form submission (default behavior)
e.preventDefault();
// write in-progress status message to ui
PEACH.flashMsg("info", "Deploying access point...");
// send activate_ap POST request
fetch("/api/v1/network/activate_ap", {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
console.log(jsonData.msg);
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
// if ap activation is successful, update the ui
if (jsonData.status === "success") {
PEACH_NETWORK.apMode();
}
})
}, false);
}
});
}
// catch click of 'Enable WiFi' and make POST request
PEACH_NETWORK.activateClient = function() {
document.addEventListener('DOMContentLoaded', function() {
var enableWifi = document.getElementById('connectWifi');
if (enableWifi) {
enableWifi.addEventListener('click', function(e) {
// prevent form submission (default behavior)
e.preventDefault();
// write in-progress status message to ui
PEACH.flashMsg("info", "Enabling WiFi client...");
// send activate_ap POST request
fetch("/api/v1/network/activate_client", {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
console.log(jsonData.msg);
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
// if client activation is successful, update the ui
if (jsonData.status === "success") {
PEACH_NETWORK.clientMode();
}
})
}, false);
}
});
}
// replace 'Deploy Access Point' button with 'Enable WiFi' button
PEACH_NETWORK.apMode = function() {
// create Enable WiFi button and add it to button div
var wifiButton = document.createElement("A");
wifiButton.className = "button center";
wifiButton.href = "/settings/network/wifi/activate";
wifiButton.id = "connectWifi";
var label = "Enable WiFi";
var buttonText = document.createTextNode(label);
wifiButton.appendChild(buttonText);
// append the new button to the buttons div
let buttons = document.getElementById("buttons");
buttons.appendChild(wifiButton);
// remove the old 'Deploy Access Point' button
let apButton = document.getElementById("deployAccessPoint");
apButton.remove();
}
// replace 'Enable WiFi' button with 'Deploy Access Point' button
PEACH_NETWORK.clientMode = function() {
// create Deploy Access Point button and add it to button div
var apButton = document.createElement("A");
apButton.className = "button center";
apButton.href = "/settings/network/ap/activate";
apButton.id = "deployAccessPoint";
var label = "Deploy Access Point";
var buttonText = document.createTextNode(label);
apButton.appendChild(buttonText);
// append the new button to the buttons div
let buttons = document.getElementById("buttons");
buttons.appendChild(apButton);
// remove the old 'Enable Wifi' button
let wifiButton = document.getElementById("connectWifi");
wifiButton.remove();
}
var networkInstance = PEACH_NETWORK;
networkInstance.activateAp();
networkInstance.activateClient();

View File

@ -1,131 +0,0 @@
/*
behavioural layer for the `network_detail.html.tera` template,
corresponding to the web route `/settings/network/wifi?<ssid>`
- intercept button clicks for connect, disconnect and forget
- perform json api call
- update the dom
methods:
PEACH_NETWORK.connect();
PEACH_NETWORK.disconnect();
PEACH_NETWORK.forget();
*/
var PEACH_NETWORK = {};
// catch click of 'Connect' button (form) and make POST request
PEACH_NETWORK.connect = function() {
document.addEventListener('DOMContentLoaded', function() {
var connectWifi = document.getElementById('connectWifi');
if (connectWifi) {
connectWifi.addEventListener('click', function(e) {
// prevent form submission (default behavior)
e.preventDefault();
// retrieve ssid value and append to form data object
var ssid = document.getElementById('connectSsid').value;
// create key:value pair
var ssidData = { ssid: ssid };
// perform json serialization
var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui
PEACH.flashMsg("info", "Connecting to access point...");
// send add_wifi POST request
fetch("/api/v1/network/wifi/connect", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
};
});
}
// catch click of 'Disconnect' button and make POST request
PEACH_NETWORK.disconnect = function() {
document.addEventListener('DOMContentLoaded', function() {
var disconnectWifi = document.getElementById('disconnectWifi');
if (disconnectWifi) {
disconnectWifi.addEventListener('click', function(e) {
// prevent form submission (default behavior)
e.preventDefault();
// retrieve ssid value and append to form data object
var ssid = document.getElementById('disconnectSsid').value;
// create key:value pair
var ssidData = { ssid: ssid };
// perform json serialization
var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui
PEACH.flashMsg("info", "Disconnecting from access point...");
// send disconnect_wifi POST request
fetch("/api/v1/network/wifi/disconnect", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
};
});
}
// catch click of 'Forget' button (form) and make POST request
PEACH_NETWORK.forget = function() {
document.addEventListener('DOMContentLoaded', function() {
var forgetWifi = document.getElementById('forgetWifi');
if (forgetWifi) {
forgetWifi.addEventListener('click', function(e) {
// prevent form submission (default behavior)
e.preventDefault();
// retrieve ssid value
var ssid = document.getElementById('forgetSsid').value;
// create key:value pair
var ssidData = { ssid: ssid };
// perform json serialization
var jsonData = JSON.stringify(ssidData);
// write in-progress status message to ui
PEACH.flashMsg("info", "Removing credentials for access point...");
// send forget_ap POST request
fetch("/api/v1/network/wifi/forget", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
};
});
}
var detailInstance = PEACH_NETWORK;
detailInstance.connect();
detailInstance.disconnect();
detailInstance.forget();

View File

@ -1,56 +0,0 @@
/*
behavioural layer for the `network_modify.html.tera` template
- intercept button click for modify (form submission of credentials)
- perform json api call
- update the dom
methods:
PEACH_NETWORK.modify();
*/
var PEACH_NETWORK = {};
// catch click of 'Save' button and make POST request
PEACH_NETWORK.modify = function() {
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior)
e.preventDefault();
// capture form data
var formElement = document.querySelector("form");
// create form data object from the wifiModify form element
var formData = new FormData(formElement);
var object = {};
// assign ssid and pass from form
formData.forEach(function(value, key){
object[key] = value;
});
// perform json serialization
var jsonData = JSON.stringify(object);
// write in-progress status message to ui
PEACH.flashMsg("info", "Updating WiFi password...");
// send new_password POST request
fetch("/api/v1/network/wifi/modify", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
});
}
var modifyInstance = PEACH_NETWORK;
modifyInstance.modify();

View File

@ -1,139 +0,0 @@
/*
behavioural layer for the `network_usage.html.tera` template,
corresponding to the web route `/settings/network/wifi/usage`
- intercept form submissions
- perform json api calls
- update the dom
methods:
PEACH_NETWORK.updateAlerts();
PEACH_NETWORK.resetUsage();
PEACH_NETWORK.toggleWarning();
PEACH_NETWORK.toggleCutoff();
*/
var PEACH_NETWORK = {};
// catch click of 'Update' and make POST request
PEACH_NETWORK.updateAlerts = function() {
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior)
e.preventDefault();
// capture form data
var formElement = document.querySelector("form");
let warn = formElement.elements.warn.value;
let cut = formElement.elements.cut.value;
let warn_flag = formElement.elements.warn_flag.checked;
let cut_flag = formElement.elements.cut_flag.checked;
// perform json serialization
var jsonData = JSON.stringify({
"warn": parseFloat(warn),
"cut": parseFloat(cut),
"warn_flag": warn_flag,
"cut_flag": cut_flag,
});
// send update_alerts POST request
fetch("/api/v1/network/wifi/usage", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
});
}
// catch click of 'Reset' and make POST request
PEACH_NETWORK.resetUsage = function() {
document.addEventListener('DOMContentLoaded', function() {
var resetBtn = document.getElementById('resetTotal');
if (resetBtn) {
resetBtn.addEventListener('click', function(e) {
// prevent form submission (default behavior)
e.preventDefault();
// send reset_data_usage POST request
fetch("/api/v1/network/wifi/usage/reset", {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
console.log(jsonData.msg);
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
// if reset is successful, update the ui
if (jsonData.status === "success") {
console.log(jsonData.data);
PEACH_NETWORK.updateTotal(jsonData.data);
}
})
}, false);
}
});
}
// update data usage total in ui
PEACH_NETWORK.updateTotal = function(data) {
document.addEventListener('DOMContentLoaded', function() {
console.log(data);
let label = document.getElementById("dataTotal");
// take usage total as bytes, convert to MB and round to nearest integer
label.textContent = (data / 1024 / 1024).round();
});
};
// update ui for warning
PEACH_NETWORK.toggleWarning = function() {
document.addEventListener('DOMContentLoaded', function() {
let i = document.getElementById("warnIcon");
let warnCheck = document.getElementById("warnCheck");
warnCheck.addEventListener('click', function(e) {
console.log('Toggling warning icon state');
if (warnCheck.checked) {
i.className = "icon";
} else {
i.className = "icon icon-inactive";
}
});
});
};
// update ui for cutoff
PEACH_NETWORK.toggleCutoff = function() {
document.addEventListener('DOMContentLoaded', function() {
let i = document.getElementById("cutIcon");
let cutCheck = document.getElementById("cutCheck");
cutCheck.addEventListener('click', function(e) {
console.log('Toggling cutoff icon state');
if (cutCheck.checked) {
i.className = "icon";
} else {
i.className = "icon icon-inactive";
}
});
});
};
var usageInstance = PEACH_NETWORK;
usageInstance.resetUsage();
usageInstance.toggleWarning();
usageInstance.toggleCutoff();
usageInstance.updateAlerts();

View File

@ -1,83 +0,0 @@
/*
behavioural layer for the `power.html.tera` template,
corresponding to the web route `/power`
- intercept button clicks for reboot & shutdown
- perform json api calls
- update the dom
methods:
PEACH_DEVICE.reboot();
PEACH_DEVICE.shutdown();
*/
var PEACH_DEVICE = {};
// catch click of 'Reboot' button and make POST request
PEACH_DEVICE.reboot = function() {
document.addEventListener('DOMContentLoaded', function() {
var rebootDevice = document.getElementById('rebootBtn');
if (rebootDevice) {
rebootDevice.addEventListener('click', function(e) {
// prevent redirect on button press (default behavior)
e.preventDefault();
// write reboot flash message
PEACH.flashMsg("success", "Rebooting the device...");
// send reboot_device POST request
fetch("/api/v1/admin/reboot", {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
console.log(jsonData.msg);
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
}
});
}
// catch click of 'Shutdown' button and make POST request
PEACH_DEVICE.shutdown = function() {
document.addEventListener('DOMContentLoaded', function() {
var shutdownDevice = document.getElementById('shutdownBtn');
if (shutdownDevice) {
shutdownDevice.addEventListener('click', function(e) {
// prevent form submission (default behavior)
e.preventDefault();
// write shutdown flash message
PEACH.flashMsg("success", "Shutting down the device...");
// send shutdown_device POST request
fetch("/api/v1/shutdown", {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
console.log(jsonData.msg);
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
}
});
}
var deviceInstance = PEACH_DEVICE;
deviceInstance.reboot();
deviceInstance.shutdown();

View File

@ -1,47 +0,0 @@
/*
* behavioural layer for the `reset_password.html.tera` template,
*/
var PEACH_AUTH = {};
// catch click of 'Save' button and make POST request
PEACH_AUTH.resetPassword = function() {
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(e) {
// prevent redirect on button press (default behavior)
e.preventDefault();
// capture form data
var formElement = document.querySelector("form");
// create form data object from the changePassword form element
var formData = new FormData(formElement);
var object = {};
// assign values from form
formData.forEach(function(value, key){
object[key] = value;
});
// perform json serialization
console.log(object);
var jsonData = JSON.stringify(object);
// write in-progress status message to ui
PEACH.flashMsg("info", "Saving new password.");
// send add_wifi POST request
fetch("/api/v1/admin/reset_password", {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: jsonData
})
.then( (response) => {
return response.json()
})
.then( (jsonData) => {
// write json response message to ui
PEACH.flashMsg(jsonData.status, jsonData.msg);
})
}, false);
});
}
var resetPassInstance = PEACH_AUTH;
resetPassInstance.resetPassword();

View File

@ -13,5 +13,4 @@
<body>
{% block nav %}{% endblock nav %}
</body>
<script type="text/javascript" src="/js/common.js"></script>
</html>

View File

@ -10,9 +10,6 @@
</div>
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %}
</div>
</div>
<script type="text/javascript" src="/js/power_menu.js"></script>
{%- endblock card -%}

View File

@ -17,9 +17,6 @@
</form>
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %}
</div>
</div>
<script type="text/javascript" src="/js/change_password.js"></script>
{%- endblock card -%}

View File

@ -35,14 +35,8 @@
<input id="changePasswordButton" class="button button-primary center" title="Add" type="submit" value="Save">
</div>
</form>
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %}
</div>
</div>
<script type="text/javascript" src="/js/reset_password.js"></script>
{%- endblock card -%}

View File

@ -15,9 +15,6 @@
</form>
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
<!-- NO SCRIPT FOR WHEN JS IS DISABLED -->
{% include "snippets/noscript" %}
</div>
</div>
<script type="text/javascript" src="/js/network_add.js"></script>
{%- endblock card -%}

View File

@ -69,7 +69,6 @@
{% include "snippets/flash_message" %}
</div>
</div>
<script type="text/javascript" src="/js/network_detail.js"></script>
{%- endif -%}
{%- endfor -%}
{%- endif -%}

View File

@ -2,9 +2,7 @@
{%- block card %}
<!-- CONFIGURE DNS FORM -->
<div class="card center">
<div class="form-container">
{% if enable_dyndns %}
<!-- DYNDNS STATUS INDICATOR -->
<div id="dyndns-status-indicator" class="stack capsule{% if is_dyndns_online %} success-border{% else %} warning-border{% endif %}">
@ -17,7 +15,6 @@
</div>
</div>
{% endif %}
<form id="configureDNS" action="/settings/network/dns" method="post">
<div class="input-wrapper">
<!-- input for externaldomain -->
@ -25,7 +22,6 @@
<label class="label-small input-label font-gray" for="external_domain" style="padding-top: 0.25rem;">External Domain (optional)</label>
<input id="external_domain" class="form-input" style="margin-bottom: 0;"
name="external_domain" type="text" title="external domain" value="{{ external_domain }}"></label>
</div>
<div class="input-wrapper">
<div>
@ -41,16 +37,13 @@
<label id="cut" class="label-small input-label font-near-black">
<label class="label-small input-label font-gray" for="cut" style="padding-top: 0.25rem;">Dynamic DNS Domain</label>
<input id="dyndns_domain" class="alert-input" name="dynamic_domain" placeholder="" type="text" title="dyndns_domain" value="{{ dyndns_subdomain }}">.dyn.peachcloud.org</label>
</div>
</div>
<div id="buttonDiv">
<input id="configureDNSButton" class="button button-primary center" title="Add" type="submit" value="Save">
</div>
</form>
<!-- FLASH MESSAGE -->
<!-- FLASH MESSAGE -->
<!-- check for flash message and display accordingly -->
{% if flash_msg and flash_name == "success" %}
<!-- display success message -->
@ -62,14 +55,6 @@
<!-- display error message -->
<div class="capsule center-text flash-message font-failure">{{ flash_msg }}.</div>
{%- endif -%}
<!-- share ux information with the user if JS is disabled -->
<noscript>
<div class="capsule flash-message info-border">
<p class="center-text">This website may be temporarily unresponsive while settings are being saved.</p>
</div>
</noscript>
</div>
</div>
<script type="text/javascript" src="/js/configure_dns.js"></script>
{%- endblock card -%}

View File

@ -43,5 +43,4 @@
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
</form>
<script type="text/javascript" src="/js/network_usage.js"></script>
{%- endblock card %}

View File

@ -20,5 +20,4 @@
<!-- FLASH MESSAGE -->
{% include "snippets/flash_message" %}
</div>
<script type="text/javascript" src="/js/network_card.js"></script>
{%- endblock card -%}

View File

@ -17,5 +17,4 @@
{% include "snippets/flash_message" %}
</div>
</div>
<script type="text/javascript" src="/js/network_modify.js"></script>
{%- endblock card -%}