use askama::Template;
use askama_web::WebTemplate;
use axum::{extract::State, response::IntoResponse};
use crate::{
telemetry::StatsSnapshot,
web::{AppState, Chrome, auth::CurrentUser, render::DomainDisplay},
};
const TOP_N: usize = 10;
impl AppState {
pub async fn dashboard(user: CurrentUser, State(state): State<AppState>) -> impl IntoResponse {
let snapshot = state.telemetry.stats.snapshot(TOP_N);
let blocklist_size = state.resolver.blocklist().len();
DashboardTemplate::new(
state.chrome("dashboard", &user).await,
snapshot,
blocklist_size,
)
}
}
#[derive(Template, WebTemplate)]
#[template(path = "dashboard.html")]
struct DashboardTemplate {
chrome: Chrome,
total: u64,
blocked: u64,
cached: u64,
forwarded: u64,
blocklist_size: String,
top_domains: Vec<(String, String)>,
top_clients: Vec<(String, String)>,
}
impl DashboardTemplate {
fn new(chrome: Chrome, snap: StatsSnapshot, blocklist_size: usize) -> Self {
Self {
chrome,
total: snap.total,
blocked: snap.blocked,
cached: snap.cached,
forwarded: snap.forwarded,
blocklist_size: group(blocklist_size as u64),
top_domains: snap
.top_domains
.into_iter()
.map(|(d, c)| (d.display_domain().to_owned(), group(c)))
.collect(),
top_clients: snap
.top_clients
.into_iter()
.map(|(ip, c)| (ip.to_string(), group(c)))
.collect(),
}
}
}
pub(crate) fn group(n: u64) -> String {
let digits = n.to_string();
let len = digits.len();
let mut out = String::with_capacity(len + len / 3);
for (i, ch) in digits.chars().enumerate() {
if i > 0 && (len - i).is_multiple_of(3) {
out.push(',');
}
out.push(ch);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn group_inserts_thousands_separators() {
assert_eq!(group(0), "0");
assert_eq!(group(42), "42");
assert_eq!(group(1_000), "1,000");
assert_eq!(group(12_403), "12,403");
assert_eq!(group(1_234_567), "1,234,567");
}
#[test]
fn template_seeds_signals_and_tables() {
let snap = StatsSnapshot {
total: 1000,
blocked: 382,
cached: 100,
forwarded: 518,
blocked_ratio: 0.382,
top_domains: vec![("ads.example.com.".to_owned(), 50)],
top_clients: vec![("192.168.1.10".parse().unwrap(), 120)],
};
let chrome = Chrome {
theme: "auto".to_owned(),
active: "dashboard",
show_nav: true,
authenticated: true,
csrf_token: "tok".to_owned(),
};
let html = DashboardTemplate::new(chrome, snap, 65432)
.render()
.expect("render");
assert!(html.contains("queries: 1000"));
assert!(html.contains("blocked: 382"));
assert!(html.contains("65,432"));
assert!(html.contains("ads.example.com"));
assert!(!html.contains("ads.example.com."));
assert!(html.contains("192.168.1.10"));
}
}