P3: routes (selected, fallback, queried)
Backend - api/routes.rs models the Babel-style route shape; metric uses an untagged enum to round-trip both numeric hop counts and the literal "infinite" string the daemon emits for poisoned routes - routes_snapshot() runs the three GETs concurrently with try_join so the snapshot is internally consistent - poller spawns a second 5s loop emitting routes://updated; both loops are owned by the Poller and aborted together on stop_daemon Frontend - routes store mirrors the snapshot shape; tabbed view (radix-vue) with selected, fallback and queried lists - RouteTable component shared by selected/fallback; metric column is colour-coded (0 green, low neutral, high yellow, infinite red) - Queried subnets show a live `expires in 12s` countdown driven by a 1Hz tick ref instead of mutating the store
This commit is contained in:
68
src-tauri/src/api/routes.rs
Normal file
68
src-tauri/src/api/routes.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use crate::api::MyceliumClient;
|
||||
use crate::error::AppResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The daemon serializes the metric as either an unsigned integer for
|
||||
/// reachable routes, or the literal string "infinite" for poisoned ones.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Metric {
|
||||
Value(u64),
|
||||
Infinite(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Route {
|
||||
pub subnet: String,
|
||||
pub next_hop: String,
|
||||
pub metric: Metric,
|
||||
pub seqno: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueriedSubnet {
|
||||
pub subnet: String,
|
||||
pub expiration: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RoutesSnapshot {
|
||||
pub selected: Vec<Route>,
|
||||
pub fallback: Vec<Route>,
|
||||
pub queried: Vec<QueriedSubnet>,
|
||||
}
|
||||
|
||||
impl MyceliumClient {
|
||||
pub async fn routes_selected(&self) -> AppResult<Vec<Route>> {
|
||||
let r = self.http().get(self.url("/admin/routes/selected")).send().await?;
|
||||
Self::parse(r).await
|
||||
}
|
||||
|
||||
pub async fn routes_fallback(&self) -> AppResult<Vec<Route>> {
|
||||
let r = self.http().get(self.url("/admin/routes/fallback")).send().await?;
|
||||
Self::parse(r).await
|
||||
}
|
||||
|
||||
pub async fn routes_queried(&self) -> AppResult<Vec<QueriedSubnet>> {
|
||||
let r = self.http().get(self.url("/admin/routes/queried")).send().await?;
|
||||
Self::parse(r).await
|
||||
}
|
||||
|
||||
pub async fn routes_snapshot(&self) -> AppResult<RoutesSnapshot> {
|
||||
// Issue the three calls concurrently so the snapshot reflects a
|
||||
// near-coincident view of the routing table.
|
||||
let (sel, fb, q) = tokio::try_join!(
|
||||
self.routes_selected(),
|
||||
self.routes_fallback(),
|
||||
self.routes_queried(),
|
||||
)?;
|
||||
Ok(RoutesSnapshot {
|
||||
selected: sel,
|
||||
fallback: fb,
|
||||
queried: q,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user