dashboard: Group messages by service
This commit is contained in:
@ -4,11 +4,11 @@ use chrono::{DateTime, Local};
|
|||||||
use eyre::{eyre, ContextCompat};
|
use eyre::{eyre, ContextCompat};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
Fatal,
|
|
||||||
Error,
|
|
||||||
Warning,
|
Warning,
|
||||||
|
Error,
|
||||||
|
Fatal,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
|
use std::{cmp::max, collections::BTreeMap};
|
||||||
|
|
||||||
use rocket::{http::Status, response::content::RawHtml, State};
|
use rocket::{http::Status, response::content::RawHtml, State};
|
||||||
use rocket_dyn_templates::{context, Template};
|
use rocket_dyn_templates::{context, Template};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use snitch_lib::Severity;
|
||||||
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
|
|
||||||
@ -9,25 +12,33 @@ pub async fn index(db: &State<Database>) -> Result<RawHtml<Template>, Status> {
|
|||||||
// TODO:
|
// TODO:
|
||||||
// maybe only show most recent error for each service in dashboard view, i.e.:
|
// 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;
|
// 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}");
|
log::error!("failed to query database: {e}");
|
||||||
Status::InternalServerError
|
Status::InternalServerError
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ServiceRecords<'a> {
|
||||||
|
first_record: &'a Record,
|
||||||
|
last_record: &'a Record,
|
||||||
|
severity: Severity,
|
||||||
|
all_records: Vec<&'a Record>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Record {
|
struct Record {
|
||||||
time: String,
|
time: String,
|
||||||
severity: String,
|
severity: Severity,
|
||||||
service: String,
|
service: String,
|
||||||
message: String,
|
message: String,
|
||||||
location: String,
|
location: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let messages: Vec<_> = messages
|
let records: Vec<_> = records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_id, record)| Record {
|
.map(|(_id, record)| Record {
|
||||||
time: record.time.map(|time| time.to_string()).unwrap_or_default(),
|
time: record.time.map(|time| time.to_string()).unwrap_or_default(),
|
||||||
severity: record.severity.as_str().to_string(),
|
severity: record.severity,
|
||||||
service: record.service,
|
service: record.service,
|
||||||
message: record.message,
|
message: record.message,
|
||||||
location: record
|
location: record
|
||||||
@ -38,10 +49,30 @@ pub async fn index(db: &State<Database>) -> Result<RawHtml<Template>, Status> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.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(
|
Ok(RawHtml(Template::render(
|
||||||
"index",
|
"index",
|
||||||
context! {
|
context! {
|
||||||
messages: &messages
|
records: &records,
|
||||||
|
records_by_service: &records_by_service,
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,26 +26,66 @@
|
|||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
font-family: 'Ubuntu Mono', mono;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
function collapse(id) {
|
||||||
|
document.getElementById(id).classList.toggle("collapsed")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{{#if records}}
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Service</th>
|
<th>Severity</th>
|
||||||
<th>Severity</th>
|
<th>Service</th>
|
||||||
<th>Time</th>
|
<th>Count</th>
|
||||||
<th>Message</th>
|
<th>Last message at</th>
|
||||||
<th>Location</th>
|
</tr>
|
||||||
</tr>
|
{{#each records_by_service}}
|
||||||
{{#each messages}}
|
<tr onclick="collapse('{{@key}}-error-list')">
|
||||||
<tr>
|
<td>{{this.severity}}</td>
|
||||||
<td>{{this.service}}</td>
|
<td>{{@key}}</td>
|
||||||
<td>{{this.severity}}</td>
|
<td>{{len this.all_records}}</td>
|
||||||
<td>{{this.time}}</td>
|
<td>{{this.last_record.time}}</td>
|
||||||
<td>{{this.message}}</td>
|
</tr>
|
||||||
<td>{{this.location}}</td>
|
<tr><td colspan="4">
|
||||||
</tr>
|
<div
|
||||||
{{/each}}
|
id="{{@key}}-error-list"
|
||||||
|
class="service-error-list collapsed"
|
||||||
|
>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Severity</th>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
{{#each this.all_records}}
|
||||||
|
<tr>
|
||||||
|
<td>{{this.severity}}</td>
|
||||||
|
<td>{{this.message}}</td>
|
||||||
|
<td>{{this.time}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</table>
|
||||||
|
</div></td></tr>
|
||||||
|
{{/each}}
|
||||||
</table>
|
</table>
|
||||||
|
{{else}}
|
||||||
|
<p>
|
||||||
|
No entries! All is well.
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user