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; #[get("/")] 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 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: Severity, service: String, message: String, location: String, } let records: Vec<_> = records .into_iter() .map(|(_id, record)| Record { time: record.time.map(|time| time.to_string()).unwrap_or_default(), severity: record.severity, service: record.service, message: record.message, location: record .file .zip(record.line) .map(|(path, line)| format!("{path}:{line}")) .unwrap_or_default(), }) .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); } Ok(RawHtml(Template::render( "index", context! { records: &records, records_by_service: &records_by_service, }, ))) }