aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/dashboard/mod.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-03 07:30:40 +0100
committertv <tv@krebsco.de>2026-11-03 07:30:40 +0100
commit09d6278fd5ce885540ff50f412fc9728322acef7 (patch)
tree9a6132bd5c3f741f228534b7e6d2e231d377acc0 /src/api/dashboard/mod.rs
parent9c080d623f2cf2d31c9aadb5107b4a9ee7847ebc (diff)
dashboard: show writer trees
Diffstat (limited to 'src/api/dashboard/mod.rs')
-rw-r--r--src/api/dashboard/mod.rs47
1 files changed, 47 insertions, 0 deletions
diff --git a/src/api/dashboard/mod.rs b/src/api/dashboard/mod.rs
index 8d954c0..6682cbd 100644
--- a/src/api/dashboard/mod.rs
+++ b/src/api/dashboard/mod.rs
@@ -490,6 +490,7 @@ pub(crate) fn router(state: AppState) -> Router<AppState> {
.route("/admin/dashboard/events", get(events))
.route("/admin/dashboard/refresh", post(refresh))
.route("/admin/dashboard/links", post(set_links))
+ .route("/admin/dashboard/tree", get(writer_tree))
.layer(Extension(Hub::new(state, closing)))
}
@@ -551,6 +552,52 @@ async fn set_links(
StatusCode::NO_CONTENT
}
+#[derive(serde::Deserialize)]
+struct TreeQuery {
+ writer: String,
+}
+
+/// `GET /admin/dashboard/tree?writer=…`: the writer's tree (`swarm::tree`), as this node
+/// computes it from the member list and the links members report (whom each reaches;
+/// one that reports none counts as reaching its neighbours by the member list): every
+/// member's parent, `null` for one without. `404` if the writer isn't a member.
+async fn writer_tree(
+ Extension(hub): Extension<Arc<Hub>>,
+ Query(query): Query<TreeQuery>,
+) -> Response {
+ let state = &hub.state;
+ let mut reaches: BTreeMap<String, BTreeSet<String>> = state
+ .links
+ .replies()
+ .into_iter()
+ .map(|(member, _, links)| (member, reached_by(&links)))
+ .collect();
+ reaches.insert(
+ state.local_id().to_owned(),
+ reached_by(&links::own_links(state).await),
+ );
+ let reached = |by: &str, member: &str| {
+ reaches
+ .get(by)
+ .is_none_or(|reached| reached.contains(member))
+ };
+ match crate::swarm::tree::whole(state, &query.writer, &reached) {
+ Some(parents) => {
+ axum::Json(json!({ "writer": query.writer, "parents": parents })).into_response()
+ }
+ None => StatusCode::NOT_FOUND.into_response(),
+ }
+}
+
+/// Whom a member reaches, by its links: those neither dormant nor down.
+fn reached_by(links: &[Link]) -> BTreeSet<String> {
+ links
+ .iter()
+ .filter(|link| !link.dormant && !link.down)
+ .map(|link| link.peer.clone())
+ .collect()
+}
+
/// `GET /admin/dashboard/state`: the latest snapshot, for a client that wants it whole.
async fn current_state(Extension(hub): Extension<Arc<Hub>>) -> Response {
hub.want();