use crate::{log::LOG, Check, Config, Service, Status, STATUS}; use chrono::{SubsecRound, Utc}; use markup::{doctype, Render}; use std::{collections::BTreeMap, ops::Deref, sync::Arc}; pub async fn make_html_page(config: Arc) -> String { let mut out = String::new(); let status = STATUS.read().await; let status = status.deref(); let log = LOG.read().await; let log = log.deref(); #[cfg(not(debug_assertions))] let css = include_str!("style.css"); #[cfg(debug_assertions)] let css = tokio::fs::read_to_string("src/style.css").await.unwrap(); let total_err = status.values().map(|s|s.status.is_err() as usize).sum::(); let total_ok = status.values().map(|s|s.status.is_ok() as usize).sum::(); markup::new! { @doctype() html { head { meta[charset="UTF-8"]; meta[name="viewport", content="width=device-width, initial-scale=1.0"]; title { "Status Page" } style { @css } } body { main { h1 { @config.title } @if total_err == 0 { div.summary.ok { "All services operational." } } else if total_ok == 0 { div.summary.error { "All services are broken." } } else { div.summary.degraded { "Degraded service. (" @total_ok " working, " @total_err " broken)" } } @for (i, service) in config.services.iter().enumerate() { @ServiceCard { i, status, service } } div.log { h2 { "Past Events" } ul { @for event in log.iter() { li.{if event.status.status.is_ok() { "ok" } else { "error" }} { @let service = &config.services[event.service]; b { @event.status.time.to_rfc2822() ": " } @service.title " " @if let Err(error) = &event.status.status { " failed. " @service.checks[event.check].display() " reported " @error } else { " is working again." } } } } } footer { "Generated " @Utc::now().to_rfc2822() " by " a[href="https://codeberg.org/metamuffin/statuspage"]{ "statuspage" } "." } } } } } .render(&mut out) .unwrap(); out } markup::define!( ServiceCard<'a>(status: &'a BTreeMap<(usize, usize), Status>, service: &'a Service, i: usize) { @let any_err = status.range((*i,usize::MIN)..(*i,usize::MAX)).any(|(_,v)|v.status.is_err()); div.service.{if any_err { "error" } else { "ok" }} { h2 { @service.title } @if let Some(url) = &service.url { a[href=url] { "Link" } } div.checks { @for (j, check) in service.checks.iter().enumerate() { @if let Some(status) = status.get(&(*i, j)) { span.name { @check.display() } @match &status.status { Ok(s) => { span.status.ok { "Ok" } span.details.ok { @s } } Err(s) => { span.status.error { "Error" } span.details.error { @s } } } span.time { @status.time.time().round_subsecs(0).to_string() } } } } } } ); impl Check { pub fn display(&self) -> String { match self { Check::Systemd(_) => "Service".to_string(), Check::Http { title, .. } => title.clone().unwrap_or("HTTP".to_string()), Check::Shell { title, .. } => title.to_owned(), Check::Pacman(_) => "Installed".to_string(), } } }