diff options
Diffstat (limited to 'src/api/dashboard/mod.rs')
| -rw-r--r-- | src/api/dashboard/mod.rs | 47 |
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(); |
