mirror of
https://github.com/Noratrieb/uptime.git
synced 2026-01-16 09:35:06 +01:00
Add timestamps to bar
This commit is contained in:
parent
f17454ae67
commit
f12b6ffa59
2 changed files with 77 additions and 21 deletions
59
src/web.rs
59
src/web.rs
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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,10 +65,18 @@
|
||||||
<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 %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue