peach-workspace/peach-web/src/routes/status/scuttlebutt.rs

286 lines
10 KiB
Rust

use maud::{html, Markup, PreEscaped};
use peach_lib::sbot::{SbotConfig, SbotStatus};
use crate::{
templates,
utils::{sbot, theme},
};
// HTML RENDERING FOR ELEMENTS
// TODO: refactor this to make better use of splices
// https://maud.lambda.xyz/splices-toggles.html
fn downtime_element(downtime: &Option<String>) -> Markup {
match downtime {
Some(time) => {
html! {
label class="label-small font-gray" for="sbotDowntime" title="go-sbot downtime" style="margin-top: 0.5rem;" { "DOWNTIME" }
p id="sbotDowntime" class="card-text" title="Downtime" { (time) }
}
}
_ => html! { (PreEscaped("")) },
}
}
fn uptime_element(uptime: &Option<String>) -> Markup {
match uptime {
Some(time) => {
html! {
label class="label-small font-gray" for="sbotUptime" title="go-sbot uptime" style="margin-top: 0.5rem;" { "UPTIME" }
p id="sbotUptime" class="card-text" title="Uptime" { (time) }
}
}
_ => html! { (PreEscaped("")) },
}
}
fn run_on_startup_element(boot_state: &Option<String>) -> Markup {
match boot_state {
Some(state) if state == "enabled" => {
html! {
p id="runOnStartup" class="card-text" title="Enabled" { "Enabled" }
}
}
_ => {
html! {
p id="runOnStartup" class="card-text" title="Disabled" { "Disabled" }
}
}
}
}
fn database_element(state: &str) -> Markup {
// retrieve the sequence number of the latest message in the sbot database
let sequence_num = sbot::latest_sequence_number();
match (state, sequence_num) {
// if the state is "active" and latest_sequence_number() was successful
("active", Ok(number)) => {
html! {
label class="card-text" style="margin-right: 5px;" { (number) }
label class="label-small font-gray" { "MESSAGES IN LOCAL DATABASE" }
}
}
(_, _) => {
html! { label class="label-small font-gray" { "DATABASE UNAVAILABLE" } }
},
}
}
fn memory_element(memory: Option<u32>) -> Markup {
let (memory, img_class, medium_label_class, small_label_class) = match memory {
Some(mem) => {
// convert memory to mb representation
let memory_rounded = mem / 1024 / 1024;
(
memory_rounded.to_string(),
"icon icon-active",
"label-medium font-normal",
"label-small font-gray",
)
}
_ => (
0.to_string(),
"icon icon-inactive",
"label-medium font-gray",
"label-small font-gray",
),
};
html! {
div class="stack" {
img class=(img_class) title="Memory" src="/icons/ram.png";
div class="flex-grid" style="padding-top: 0.5rem;" {
label class=(medium_label_class) style="padding-right: 3px;" title="Memory usage of the go-sbot process in MB" { (memory) }
label class=(small_label_class) { "MB" }
}
label class=(small_label_class) { "MEMORY" }
}
}
}
fn hops_element() -> Markup {
// retrieve go-sbot systemd process status
let hops = match SbotConfig::read() {
Ok(conf) => conf.hops,
_ => 0,
};
html! {
div class="stack" {
img class="icon icon-active" title="Hops" src="/icons/orbits.png";
div class="flex-grid" style="padding-top: 0.5rem;" {
label class="label-medium font-normal" style="padding-right: 3px;" title="Replication hops" {
(hops)
}
}
label class="label-small font-gray" { "HOPS" }
}
}
}
fn blobs_element(blobstore: Option<u64>) -> Markup {
let blobstore_size = match blobstore {
// convert blobstore size to mb representation
Some(blobs) => blobs / 1024 / 1024,
None => 0,
};
html! {
div class="stack" {
img class="icon icon-active" title="Blobs" src="/icons/image-file.png";
div class="flex-grid" style="padding-top: 0.5rem;" {
label class="label-medium font-normal" style="padding-right: 3px;" title="Blobstore size in MB" { (blobstore_size) }
label class="label-small font-normal" { "MB" }
}
label class="label-small font-gray" { "BLOBSTORE" }
}
}
}
/// Read the state of the go-sbot process and define status-related
/// elements accordingly.
fn render_status_elements<'a>() -> (
String,
&'a str,
&'a str,
Markup,
Markup,
Markup,
Markup,
Markup,
) {
// retrieve go-sbot systemd process status
match SbotStatus::read() {
Ok(status) if status.state == Some("active".to_string()) => (
"ACTIVE".to_string(),
"capsule capsule-container border-success",
"center icon icon-active",
uptime_element(&status.uptime),
run_on_startup_element(&status.boot_state),
database_element("active"),
memory_element(status.memory),
blobs_element(status.blobstore),
),
Ok(status) if status.state == Some("inactive".to_string()) => (
"INACTIVE".to_string(),
"capsule capsule-container border-warning",
"center icon icon-inactive",
downtime_element(&status.downtime),
run_on_startup_element(&status.boot_state),
database_element("inactive"),
memory_element(None),
blobs_element(status.blobstore),
),
// state is neither active nor inactive (might be "failed")
Ok(status) if status.state.is_some() => (
status.state.unwrap().to_uppercase(),
"capsule capsule-container border-danger",
"center icon icon-inactive",
downtime_element(&None),
run_on_startup_element(&status.boot_state),
database_element("failed"),
memory_element(None),
blobs_element(status.blobstore),
),
Ok(status) if status.state.is_none() => (
"UNAVAILABLE".to_string(),
"capsule capsule-container border-danger",
"center icon icon-inactive",
downtime_element(&None),
run_on_startup_element(&status.boot_state),
database_element("unavailable"),
memory_element(None),
blobs_element(status.blobstore),
),
_ => (
"PROCESS QUERY FAILED".to_string(),
"capsule capsule-container border-danger",
"center icon icon-inactive",
downtime_element(&None),
run_on_startup_element(&None),
database_element("error"),
memory_element(None),
blobs_element(None),
),
}
}
/// Scuttlebutt status template builder.
pub fn build_template() -> PreEscaped<String> {
let (
sbot_state,
capsule_class,
sbot_icon_class,
uptime_downtime_element,
run_on_startup_element,
database_element,
memory_element,
blobs_element,
) = render_status_elements();
let hops_element = hops_element();
let status_template = html! {
(PreEscaped("<!-- SCUTTLEBUTT STATUS -->"))
div class="card center" {
(PreEscaped("<!-- SBOT INFO BOX -->"))
div class=(capsule_class) {
(PreEscaped("<!-- SBOT STATUS GRID -->"))
div class="two-grid" title="go-sbot process state" {
(PreEscaped("<!-- top-right config icon -->"))
a class="link two-grid-top-right" href="/settings/scuttlebutt" title="Configure Scuttlebutt settings" {
img id="configureNetworking" class="icon-small icon-active" src="/icons/cog.svg" alt="Configure";
}
(PreEscaped("<!-- left column -->"))
(PreEscaped("<!-- go-sbot state icon with label -->"))
div class="grid-column-1" {
img id="sbotStateIcon" class=(sbot_icon_class) src="/icons/hermies.svg" alt="Hermies";
label id="sbotStateLabel" for="sbotStateIcon" class="center label-small font-gray" style="margin-top: 0.5rem;" title="Sbot state" {
(sbot_state)
}
}
(PreEscaped("<!-- right column -->"))
(PreEscaped("<!-- go-sbot version and uptime / downtime with labels -->"))
div class="grid-column-2" {
label class="label-small font-gray" for="sbotVersion" title="go-sbot version" {
"VERSION"
}
p id="sbotVersion" class="card-text" title="Version" {
"1.1.0-alpha"
}
(uptime_downtime_element)
label class="label-small font-gray" for="sbotBootState" title="go-sbot boot state" style="margin-top: 0.5rem;" { "RUN ON STARTUP" }
(run_on_startup_element)
}
}
hr style="color: var(--light-gray);";
div id="middleSection" style="margin-top: 1rem;" {
div id="sbotInfo" class="center" style="display: flex; justify-content: space-between; width: 90%;" {
div class="center" style="display: flex; align-items: last baseline;" {
(database_element)
}
}
}
hr style="color: var(--light-gray);";
(PreEscaped("<!-- THREE-ACROSS STACK -->"))
div class="three-grid card-container" style="margin-top: 1rem;" {
(hops_element)
(blobs_element)
(memory_element)
}
}
}
};
// wrap the nav bars around the scuttlebutt status card template content
// parameters are template, title and back url
let body = templates::nav::build_template(status_template, "Status", Some("/"));
// query the current theme so we can pass it into the base template builder
let theme = theme::get_theme();
// render the base template with the provided body
templates::base::build_template(body, theme)
}