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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
use crate::{log::LOG, 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<Config>) -> 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::<usize>();
let total_ok = status.values().map(|s|s.status.is_ok() as usize).sum::<usize>();
markup::new! {
@doctype()
html {
head {
meta[charset="UTF-8"];
meta[name="viewport", content="width=device-width, initial-scale=1.0"];
title { @config.title }
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()
}
}
}
}
}
}
);
|