diff --git a/snitch-lib/src/message.rs b/snitch-lib/src/message.rs index 4c8936d..ba0b595 100644 --- a/snitch-lib/src/message.rs +++ b/snitch-lib/src/message.rs @@ -4,11 +4,11 @@ use chrono::{DateTime, Local}; use eyre::{eyre, ContextCompat}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub enum Severity { - Fatal, - Error, Warning, + Error, + Fatal, } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/snitch-web/src/dashboard.rs b/snitch-web/src/dashboard.rs index df7a037..45bbfdf 100644 --- a/snitch-web/src/dashboard.rs +++ b/snitch-web/src/dashboard.rs @@ -1,6 +1,9 @@ +use std::{cmp::max, collections::BTreeMap}; + use rocket::{http::Status, response::content::RawHtml, State}; use rocket_dyn_templates::{context, Template}; use serde::Serialize; +use snitch_lib::Severity; use crate::database::Database; @@ -9,25 +12,33 @@ pub async fn index(db: &State) -> Result, Status> { // TODO: // maybe only show most recent error for each service in dashboard view, i.e.: // select distinct on (service) time, message from (select * from record order by time desc) as foo; - let messages = db.get_all_log_messages().map_err(|e| { + let records = db.get_all_log_messages().map_err(|e| { log::error!("failed to query database: {e}"); Status::InternalServerError })?; + #[derive(Serialize)] + struct ServiceRecords<'a> { + first_record: &'a Record, + last_record: &'a Record, + severity: Severity, + all_records: Vec<&'a Record>, + } + #[derive(Serialize)] struct Record { time: String, - severity: String, + severity: Severity, service: String, message: String, location: String, } - let messages: Vec<_> = messages + let records: Vec<_> = records .into_iter() .map(|(_id, record)| Record { time: record.time.map(|time| time.to_string()).unwrap_or_default(), - severity: record.severity.as_str().to_string(), + severity: record.severity, service: record.service, message: record.message, location: record @@ -38,10 +49,30 @@ pub async fn index(db: &State) -> Result, Status> { }) .collect(); + let mut records_by_service = BTreeMap::new(); + + for record in &records { + let entry = records_by_service + .entry(record.service.as_str()) + .or_insert_with(|| ServiceRecords { + first_record: record, + last_record: record, + all_records: vec![], + severity: record.severity, + }); + + entry.last_record = record; + entry.all_records.push(record); + entry.severity = max(record.severity, entry.severity); + } + + log::error!("records_by_service, len={}", records_by_service.len()); + Ok(RawHtml(Template::render( "index", context! { - messages: &messages + records: &records, + records_by_service: &records_by_service, }, ))) } diff --git a/snitch-web/templates/index.hbs b/snitch-web/templates/index.hbs index d38b3ae..c891441 100644 --- a/snitch-web/templates/index.hbs +++ b/snitch-web/templates/index.hbs @@ -26,26 +26,66 @@ padding: 0.5em; font-family: 'Ubuntu Mono', mono; } + + .service-error-list { + max-height: 20em; + font-size: small; + overflow: scroll; + transition: max-height ease-in-out 0.4s; + } + + .collapsed { + max-height: 0; + } + + {{#if records}} - - - - - - - - {{#each messages}} - - - - - - - - {{/each}} + + + + + + + {{#each records_by_service}} + + + + + + + + {{/each}}
ServiceSeverityTimeMessageLocation
{{this.service}}{{this.severity}}{{this.time}}{{this.message}}{{this.location}}
SeverityServiceCountLast message at
{{this.severity}}{{@key}}{{len this.all_records}}{{this.last_record.time}}
+
+ {{else}} +

+ No entries! All is well. +

+ {{/if}}