add scuttlebutt settings menu

This commit is contained in:
glyph 2022-03-11 15:33:04 +02:00
parent 07c18ea64d
commit eba15605c2
6 changed files with 58 additions and 287 deletions

View File

@ -132,6 +132,10 @@ fn main() {
Response::html(routes::settings::menu::build())
},
(GET) (/settings/scuttlebutt) => {
Response::html(routes::settings::scuttlebutt::menu::build())
},
// The code block is called if none of the other blocks matches the request.
// We return an empty response with a 404 status code.
_ => Response::empty_404()

View File

@ -4,7 +4,7 @@ use crate::{templates, CONFIG};
// TODO: flash message implementation for rouille
//
/// Main settings menu template builder.
/// Settings menu template builder.
pub fn build() -> PreEscaped<String> {
let menu_template = html! {
(PreEscaped("<!-- SETTINGS MENU -->"))

View File

@ -2,5 +2,5 @@
//pub mod dns;
pub mod menu;
//pub mod network;
//pub mod scuttlebutt;
pub mod scuttlebutt;
//pub mod theme;

View File

@ -1,285 +0,0 @@
use std::{
io,
process::{Command, Output},
};
use log::{info, warn};
use peach_lib::sbot::{SbotConfig, SbotStatus};
use rocket::{
form::{Form, FromForm},
get, post,
request::FlashMessage,
response::{Flash, Redirect},
};
use rocket_dyn_templates::{tera::Context, Template};
use crate::routes::authentication::Authenticated;
use crate::utils;
#[derive(Debug, FromForm)]
pub struct SbotConfigForm {
/// Directory path for the log and indexes.
repo: String,
/// Directory path for writing debug output.
debugdir: String,
/// Secret-handshake app-key (aka. network key).
shscap: String,
/// HMAC hash used to sign messages.
hmac: String,
/// Replication hops (1: friends, 2: friends of friends).
hops: u8,
/// IP address to listen on.
lis_ip: String,
/// Port to listen on.
lis_port: String,
/// Address to listen on for WebSocket connections.
wslis: String,
/// Address to for metrics and pprof HTTP server.
debuglis: String,
/// Enable sending local UDP broadcasts.
localadv: bool,
/// Enable listening for UDP broadcasts and connecting.
localdiscov: bool,
/// Enable syncing by using epidemic-broadcast-trees (EBT).
enable_ebt: bool,
/// Bypass graph auth and fetch remote's feed (useful for pubs that are restoring their data
/// from peer; user beware - caveats about).
promisc: bool,
/// Disable the UNIX socket RPC interface.
nounixsock: bool,
/// Run the go-sbot on system start-up (systemd service enabled).
startup: bool,
/// Attempt to repair the filesystem before starting.
repair: bool,
}
// HELPERS AND ROUTES FOR /settings/scuttlebutt
/// Scuttlebutt settings menu.
#[get("/")]
pub fn ssb_settings_menu(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
// retrieve current ui theme
let theme = utils::get_theme();
// retrieve go-sbot systemd process status
let sbot_status = SbotStatus::read().ok();
let mut context = Context::new();
context.insert("theme", &theme);
context.insert("sbot_status", &sbot_status);
context.insert("back", &Some("/settings".to_string()));
context.insert("title", &Some("Scuttlebutt Settings".to_string()));
if let Some(flash) = flash {
context.insert("flash_name", &Some(flash.kind().to_string()));
context.insert("flash_msg", &Some(flash.message().to_string()));
};
Template::render("settings/scuttlebutt/menu", &context.into_json())
}
/// Sbot configuration page (includes form for updating configuration parameters).
#[get("/configure")]
pub fn configure_sbot(flash: Option<FlashMessage>, _auth: Authenticated) -> Template {
// retrieve current ui theme
let theme = utils::get_theme();
// retrieve go-sbot systemd process status
let sbot_status = SbotStatus::read().ok();
let run_on_startup = sbot_status.map(|status| status.boot_state);
// retrieve sbot config parameters
let sbot_config = SbotConfig::read().ok();
let mut context = Context::new();
context.insert("theme", &theme);
context.insert("back", &Some("/settings/scuttlebutt".to_string()));
context.insert("title", &Some("Sbot Configuration".to_string()));
context.insert("sbot_config", &sbot_config);
context.insert("run_on_startup", &Some(run_on_startup));
if let Some(flash) = flash {
context.insert("flash_name", &Some(flash.kind().to_string()));
context.insert("flash_msg", &Some(flash.message().to_string()));
};
Template::render("settings/scuttlebutt/configure_sbot", &context.into_json())
}
// TODO: consider using `Contextual` here to collect all submitted form
// fields to re-render forms with submitted values on error
/// Receive the sbot configuration form data and save it to file.
#[post("/configure?<restart>", data = "<config>")]
pub fn configure_sbot_post(
restart: bool,
config: Form<SbotConfigForm>,
_auth: Authenticated,
) -> Flash<Redirect> {
// call `into_inner()` to take ownership of the `config` data
let owned_config = config.into_inner();
// concat the ip and port for listen address
let lis = format!("{}:{}", owned_config.lis_ip, owned_config.lis_port);
// instantiate `SbotConfig` from form data
let config = SbotConfig {
lis,
hops: owned_config.hops,
repo: owned_config.repo,
debugdir: owned_config.debugdir,
shscap: owned_config.shscap,
localadv: owned_config.localadv,
localdiscov: owned_config.localdiscov,
hmac: owned_config.hmac,
wslis: owned_config.wslis,
debuglis: owned_config.debuglis,
enable_ebt: owned_config.enable_ebt,
promisc: owned_config.promisc,
nounixsock: owned_config.nounixsock,
repair: owned_config.repair,
};
match owned_config.startup {
true => {
info!("Enabling go-sbot.service");
if let Err(e) = systemctl_sbot_cmd("enable") {
warn!("Failed to enable go-sbot.service: {}", e)
}
}
false => {
info!("Disabling go-sbot.service");
if let Err(e) = systemctl_sbot_cmd("disable") {
warn!("Failed to disable go-sbot.service: {}", e)
}
}
};
// write config to file
match SbotConfig::write(config) {
Ok(_) => {
// if `restart` query parameter is `true`, attempt sbot process (re)start
if restart {
restart_sbot_process(
// redirect url
"/settings/scuttlebutt/configure",
// success flash msg
"Updated configuration and restarted the sbot process",
// first failed flash msg
"Updated configuration but failed to start the sbot process",
// second failed flash msg
"Updated configuration but failed to stop the sbot process",
)
} else {
Flash::success(
Redirect::to("/settings/scuttlebutt/configure"),
"Updated configuration",
)
}
}
Err(e) => Flash::error(
Redirect::to("/settings/scuttlebutt/configure"),
format!("Failed to update configuration: {}", e),
),
}
}
/// Set default configuration parameters for the go-sbot and save them to file.
#[get("/configure/default")]
pub fn configure_sbot_default(_auth: Authenticated) -> Flash<Redirect> {
let default_config = SbotConfig::default();
// write default config to file
match SbotConfig::write(default_config) {
Ok(_) => Flash::success(
Redirect::to("/settings/scuttlebutt/configure"),
"Restored default configuration",
),
Err(e) => Flash::error(
Redirect::to("/settings/scuttlebutt/configure"),
format!("Failed to restore default configuration: {}", e),
),
}
}
/// Attempt to start the go-sbot.service process.
/// Redirect to the Scuttlebutt settings menu and communicate the outcome of
/// the attempt via a flash message.
#[get("/start")]
pub fn start_sbot(_auth: Authenticated) -> Flash<Redirect> {
info!("Starting go-sbot.service");
match systemctl_sbot_cmd("start") {
Ok(_) => Flash::success(
Redirect::to("/settings/scuttlebutt"),
"Sbot process has been started",
),
Err(_) => Flash::error(
Redirect::to("/settings/scuttlebutt"),
"Failed to start the sbot process",
),
}
}
/// Attempt to stop the go-sbot.service process.
/// Redirect to the Scuttlebutt settings menu and communicate the outcome of
/// the attempt via a flash message.
#[get("/stop")]
pub fn stop_sbot(_auth: Authenticated) -> Flash<Redirect> {
info!("Stopping go-sbot.service");
match systemctl_sbot_cmd("stop") {
Ok(_) => Flash::success(
Redirect::to("/settings/scuttlebutt"),
"Sbot process has been stopped",
),
Err(_) => Flash::error(
Redirect::to("/settings/scuttlebutt"),
"Failed to stop the sbot process",
),
}
}
/// Attempt to restart the go-sbot.service process.
/// Redirect to the Scuttlebutt settings menu and communicate the outcome of
/// the attempt via a flash message.
#[get("/restart")]
pub fn restart_sbot(_auth: Authenticated) -> Flash<Redirect> {
restart_sbot_process(
"/settings/scuttlebutt",
"Sbot process has been restarted",
"Failed to start the sbot process",
"Failed to stop the sbot process",
)
}
// HELPER FUNCTIONS
/// Executes a systemctl command for the go-sbot.service process.
pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result<Output> {
Command::new("systemctl")
.arg("--user")
.arg(cmd)
.arg("go-sbot.service")
.output()
}
/// Executes a systemctl stop command followed by start command.
/// Returns a redirect with a flash message stating the output of the restart attempt.
fn restart_sbot_process(
redirect_url: &str,
success_msg: &str,
start_failed_msg: &str,
stop_failed_msg: &str,
) -> Flash<Redirect> {
let url = redirect_url.to_string();
info!("Restarting go-sbot.service");
match systemctl_sbot_cmd("stop") {
// if stop was successful, try to start the process
Ok(_) => match systemctl_sbot_cmd("start") {
Ok(_) => Flash::success(Redirect::to(url), success_msg),
Err(e) => Flash::error(Redirect::to(url), format!("{}: {}", start_failed_msg, e)),
},
Err(e) => Flash::error(Redirect::to(url), format!("{}: {}", stop_failed_msg, e)),
}
}

View File

@ -0,0 +1,51 @@
use maud::{html, PreEscaped};
use peach_lib::sbot::SbotStatus;
use crate::templates;
// TODO: flash message implementation for rouille
//
/// Read the status of the go-sbot service and render buttons accordingly.
fn render_process_buttons() -> PreEscaped<String> {
// retrieve go-sbot systemd process status
let sbot_status = SbotStatus::read();
html! {
// render the stop and restart buttons if sbot process is currently active
@if let Ok(status) = sbot_status {
@if status.state == Some("active".to_string()) {
a id="stop" class="button button-primary center" href="/settings/scuttlebutt/stop" title="Stop Sbot" { "Stop Sbot" }
a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot" { "Restart Sbot" }
// render the start button if sbot process is currently inactive
} @else {
a id="start" class="button button-primary center" href="/settings/scuttlebutt/start" title="Start Sbot" { "Start Sbot" }
}
// render the start button if an error was returned by the status query
} @else {
a id="start" class="button button-primary center" href="/settings/scuttlebutt/start" title="Start Sbot" { "Start Sbot" }
}
}
}
/// Scuttlebutt settings menu template builder.
pub fn build() -> PreEscaped<String> {
let menu_template = html! {
(PreEscaped("<!-- SCUTTLEBUTT SETTINGS MENU -->"))
div class="card center" {
(PreEscaped("<!-- BUTTONS -->"))
div id="settingsButtons" {
a id="configureSbot" class="button button-primary center" href="/settings/scuttlebutt/configure" title="Configure Sbot" { "Configure Sbot" }
// conditionally render the start / stop / restart buttons
(render_process_buttons())
}
}
};
// wrap the nav bars around the settings menu template content
// parameters are template, title and back url
let body = templates::nav::build(menu_template, "Scuttlebutt Settings", Some("/settings"));
// render the base template with the provided body
templates::base::build(body)
}

View File

@ -0,0 +1 @@
pub mod menu;