aboutsummaryrefslogtreecommitdiff
path: root/src/web.rs
blob: 008069767e32dca05f9d2d4fde3a73a66746d30e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use crate::{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<Config>) -> Html<String> {
    let mut out = String::new();
    let status = STATUS.read().await;
    let status = status.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 }
                    }
                }
            }
        }
    }
    .render(&mut out)
    .unwrap();
    Html(out)
}

markup::define!(
    ServiceCard<'a>(status: &'a BTreeMap<(usize, usize), Result<Success>>, 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 }
            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())
    }
}