use crate::{log::LOG, Check, Config, Service, Success, STATUS}; use anyhow::Result; use axum::response::Html; use markup::{doctype, Render}; use std::{collections::BTreeMap, ops::Deref, sync::Arc, time::Duration}; pub async fn send_html_page(config: Arc) -> Html { 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(); 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 } @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.error.is_some() { "error" } else { "ok" }} { @let service = &config.services[event.service]; b { @event.time.to_rfc2822() ": " } @service.title " " @if let Some(error) = &event.error { " failed. " @service.checks[event.check].display() " reported " @error } else { " is working again." } } } } } } } } } .render(&mut out) .unwrap(); Html(out) } markup::define!( ServiceCard<'a>(status: &'a BTreeMap<(usize, usize), Result>, service: &'a Service, i: usize) { @let any_err = status.range((*i,usize::MIN)..(*i,usize::MAX)).any(|(_,v)|v.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 { Ok(Success { latency, updated }) => { span.status.ok { "Ok" } span.details.ok { "Checked " @format_duration(updated.elapsed().unwrap_or_default()) " ago" @if let Some(latency) = latency { ", took " @format_duration(*latency) } "." } } Err(e) => { span.status.error { "Error" } span.details { @format!("{e}") } } } } } } } } ); 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(), } } } fn format_duration(d: Duration) -> String { if d.as_millis() < 1500 { format!("{}ms", d.as_millis()) } else { format!("{}s", d.as_secs()) } }