use axum::extract::{Path, Query, State};
use axum::response::{Html, IntoResponse, Json};
use axum::http::StatusCode;
use cybergraph::Cybergraph;
use serde_json::json;
use std::collections::BTreeMap;
use crate::Shared;
pub async fn dash() -> Html<&'static str> {
Html(include_str!("../static/cybergraph_dash.html"))
}
pub async fn get_report(
State(state): State<Shared>,
Path(name): Path<String>,
Query(q): Query<BTreeMap<String, String>>,
) -> impl IntoResponse {
let app = state.lock().expect("lock");
let limit = q.get("limit").and_then(|v| v.parse().ok()).unwrap_or(20usize);
let out = match name.as_str() {
"overview" => overview(&app.cell),
"chains" => chains(&app.cell, limit),
"recent_signals" => recent_signals(&app.cell, limit),
"networks" => networks(&app.cell, limit),
"size" => size_bytes(&app.cell),
_ => return (StatusCode::NOT_FOUND, "unknown report").into_response(),
};
Json(out).into_response()
}
pub fn overview(cell: &Cybergraph) -> serde_json::Value {
let neurons_with_chains = cell.chains.len();
let total_signals: usize = cell.chains.values().map(|c| c.entries.len()).sum();
let networks: std::collections::BTreeSet<[u8; 32]> = cell
.chains
.values()
.flat_map(|c| c.entries.values().map(|s| s.network))
.collect();
json!({
"neurons_with_chains": neurons_with_chains,
"total_signals": total_signals,
"distinct_networks": networks.len(),
})
}
pub fn chains(cell: &Cybergraph, limit: usize) -> serde_json::Value {
let mut rows: Vec<(String, usize, u64, u64, String)> = cell
.chains
.iter()
.filter_map(|(neuron, chain)| {
let (&step, tip) = chain.entries.iter().next_back()?;
Some((hex::encode(neuron), chain.entries.len(), step, tip.height, hex::encode(tip.hash())))
})
.collect();
rows.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
rows.truncate(limit);
json!(rows
.into_iter()
.map(|(neuron, length, tip_step, tip_height, tip_hash)| json!({
"neuron": neuron, "length": length, "tip_step": tip_step,
"tip_height": tip_height, "tip_hash": tip_hash,
}))
.collect::<Vec<_>>())
}
pub fn recent_signals(cell: &Cybergraph, limit: usize) -> serde_json::Value {
let mut rows: Vec<serde_json::Value> = cell
.chains
.iter()
.flat_map(|(neuron, chain)| {
chain.entries.values().map(move |s| {
let links: Vec<_> = s
.links
.iter()
.map(|l| {
json!({
"from": hex::encode(l.from),
"to": hex::encode(l.to),
"token": hex::encode(l.token),
"amount": l.amount,
"valence": l.valence,
})
})
.collect();
json!({
"neuron": hex::encode(neuron),
"step": s.step,
"height": s.height,
"network": hex::encode(s.network),
"prev": hex::encode(s.prev),
"hash": hex::encode(s.hash()),
"link_count": s.links.len(),
"links": links,
})
})
})
.collect();
rows.sort_by(|a, b| {
b["height"].as_u64().cmp(&a["height"].as_u64()).then(b["step"].as_u64().cmp(&a["step"].as_u64()))
});
rows.truncate(limit);
json!(rows)
}
pub fn networks(cell: &Cybergraph, limit: usize) -> serde_json::Value {
let mut counts: std::collections::BTreeMap<String, u64> = std::collections::BTreeMap::new();
for chain in cell.chains.values() {
for s in chain.entries.values() {
*counts.entry(hex::encode(s.network)).or_default() += 1;
}
}
let mut rows: Vec<(String, u64)> = counts.into_iter().collect();
rows.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
rows.truncate(limit);
json!(rows.into_iter().map(|(network, signals)| json!({"network": network, "signals": signals})).collect::<Vec<_>>())
}
pub fn size_bytes(cell: &Cybergraph) -> serde_json::Value {
const KEY: usize = 32;
let mut signal_shells = 0usize;
let mut cyberlinks = 0usize;
let mut delta_pi = 0usize;
for chain in cell.chains.values() {
for signal in chain.entries.values() {
signal_shells += 8 + std::mem::size_of_val(signal); cyberlinks += signal.links.iter().map(std::mem::size_of_val).sum::<usize>();
delta_pi += signal.delta_pi.iter().map(std::mem::size_of_val).sum::<usize>();
}
}
let chain_keys = cell.chains.len() * KEY;
let total = signal_shells + cyberlinks + delta_pi + chain_keys;
json!({
"signal_shells": signal_shells,
"cyberlinks": cyberlinks,
"delta_pi": delta_pi,
"chain_keys": chain_keys,
"total": total,
"estimate": true,
})
}