Add timestamps to bar

This commit is contained in:
nora 2023-09-02 18:36:27 +02:00
parent f17454ae67
commit f12b6ffa59
2 changed files with 77 additions and 21 deletions

View file

@ -14,6 +14,16 @@ use sqlx::{Pool, Sqlite};
use crate::{client::CheckState, db::Check}; use crate::{client::CheckState, db::Check};
trait RenderDate {
fn render_nicely(&self) -> String;
}
impl RenderDate for chrono::DateTime<Utc> {
fn render_nicely(&self) -> String {
self.to_rfc3339_opts(chrono::SecondsFormat::Millis, /*use_z*/ true)
}
}
pub async fn axum_server(db: Arc<Pool<Sqlite>>) -> Result<()> { pub async fn axum_server(db: Arc<Pool<Sqlite>>) -> Result<()> {
let app = Router::new().route("/", get(root)).with_state(db); let app = Router::new().route("/", get(root)).with_state(db);
@ -66,7 +76,7 @@ fn compute_status(checks: Vec<Check>) -> Vec<WebsiteStatus> {
let mut count_ok = 0; let mut count_ok = 0;
const BAR_ELEMS: usize = 100; const BAR_ELEMS: usize = 100;
let bar_classes = checks_to_classes(&checks, BAR_ELEMS); let bar_info = checks_to_classes(&checks, BAR_ELEMS);
let len = checks.len(); let len = checks.len();
checks.into_iter().for_each(|(time, result)| { checks.into_iter().for_each(|(time, result)| {
@ -79,20 +89,20 @@ fn compute_status(checks: Vec<Check>) -> Vec<WebsiteStatus> {
let ok_ratio = (count_ok as f32) / (len as f32); let ok_ratio = (count_ok as f32) / (len as f32);
let ok_ratio = format!("{:.2}%", ok_ratio * 100.0); let ok_ratio = format!("{:.2}%", ok_ratio * 100.0);
let last_ok = last_ok let last_ok = last_ok.map(|utc| utc.render_nicely());
.map(|utc| utc.to_rfc3339_opts(chrono::SecondsFormat::Millis, /*use_z*/ true));
WebsiteStatus { WebsiteStatus {
website, website,
last_ok, last_ok,
ok_ratio, ok_ratio,
count_ok, count_ok,
total_requests: len, total_requests: len,
bar_classes, bar_info,
} }
}) })
.collect() .collect()
} }
#[derive(Debug)]
enum BarClass { enum BarClass {
Green, Green,
Orange, Orange,
@ -111,28 +121,39 @@ impl BarClass {
} }
} }
#[derive(Debug)]
struct BarInfo {
elems: Vec<BarClass>,
first_time: Option<DateTime<Utc>>,
last_time: Option<DateTime<Utc>>,
}
/// Converts a list of (sorted by time) checks at arbitrary dates into a list of boxes for the /// Converts a list of (sorted by time) checks at arbitrary dates into a list of boxes for the
/// frontend, in a fixed sensical timeline. /// frontend, in a fixed sensical timeline.
/// We slice the time from the first check to the last check (maybe something like last check-30d /// We slice the time from the first check to the last check (maybe something like last check-30d
/// in the future) into slices and aggregate all checks from these times into these slices. /// in the future) into slices and aggregate all checks from these times into these slices.
fn checks_to_classes(checks: &[(DateTime<Utc>, CheckState)], classes: usize) -> Vec<BarClass> { fn checks_to_classes(checks: &[(DateTime<Utc>, CheckState)], classes: usize) -> BarInfo {
assert_ne!(classes, 0); assert_ne!(classes, 0);
let Some(first) = checks.first() else { let Some(first) = checks.first() else {
return vec![]; return BarInfo {
elems: Vec::new(),
first_time: None,
last_time: None,
};
}; };
let last = checks.last().unwrap(); let last = checks.last().unwrap();
let mut bins = vec![vec![]; classes]; let mut bins = vec![vec![]; classes];
let first = first.0.timestamp_millis(); let first_m = first.0.timestamp_millis();
let last = last.0.timestamp_millis(); let last_m = last.0.timestamp_millis();
let last_rel = last - first; let last_rel = last_m - first_m;
assert!(last.is_positive(), "checks not ordered correctly"); assert!(last_m.is_positive(), "checks not ordered correctly");
for check in checks { for check in checks {
let time_rel = check.0.timestamp_millis() - first; let time_rel = check.0.timestamp_millis() - first_m;
assert!(first.is_positive(), "checks not ordered correctly"); assert!(first_m.is_positive(), "checks not ordered correctly");
/* /*
5 bins: 5 bins:
@ -147,7 +168,8 @@ fn checks_to_classes(checks: &[(DateTime<Utc>, CheckState)], classes: usize) ->
bins[bin].push(check); bins[bin].push(check);
} }
bins.iter() let elems = bins
.iter()
.map(|checks| { .map(|checks| {
let ok = checks let ok = checks
.iter() .iter()
@ -167,16 +189,23 @@ fn checks_to_classes(checks: &[(DateTime<Utc>, CheckState)], classes: usize) ->
unreachable!("i dont think logic works like this") unreachable!("i dont think logic works like this")
} }
}) })
.collect() .collect();
BarInfo {
elems,
first_time: Some(first.0),
last_time: Some(last.0),
}
} }
#[derive(Debug)]
struct WebsiteStatus { struct WebsiteStatus {
website: String, website: String,
last_ok: Option<String>, last_ok: Option<String>,
ok_ratio: String, ok_ratio: String,
total_requests: usize, total_requests: usize,
count_ok: usize, count_ok: usize,
bar_classes: Vec<BarClass>, bar_info: BarInfo,
} }
#[derive(Template)] #[derive(Template)]

View file

@ -10,13 +10,32 @@
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
} }
.check-result-bar-container {
display: flex;
flex-direction: column;
width: 800px;
}
/* This is a bit hacky and ensures that the bar doesn't go through the right. */
@media only screen and (max-width: 810px) {
.check-result-bar-container {
width: 100%;
}
}
.check-result-bar { .check-result-bar {
display: flex; display: flex;
} }
.check-result-bar-labels {
margin-top: 5px;
display: flex;
justify-content: space-between;
}
.check-result { .check-result {
height: 10px; height: 10px;
width: 5px; width: 100vw; /* It will be squashed. */
} }
.check-result-red { .check-result-red {
@ -46,13 +65,21 @@
<p>Last OK: <span class="utc-timestamp">{{ check.last_ok.as_deref().unwrap() }}</span></p> <p>Last OK: <span class="utc-timestamp">{{ check.last_ok.as_deref().unwrap() }}</span></p>
{% endif %} {% endif %}
<div class="check-result-bar"> <div class="check-result-bar-container">
{% for result in check.bar_classes %} <div class="check-result-bar">
<div class="check-result {{ result.as_class() }}"></div> {% for result in check.bar_info.elems %}
{% endfor %} <div class="check-result {{ result.as_class() }}"></div>
{% endfor %}
</div>
{% if check.bar_info.first_time.is_some() && check.bar_info.last_time.is_some() %}
<div class="check-result-bar-labels">
<span class="utc-timestamp">{{ check.bar_info.first_time.unwrap().render_nicely() }}</span>
<span class="utc-timestamp">{{ check.bar_info.last_time.unwrap().render_nicely() }}</span>
</div>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</main> </main>
<script> <script>