Files
Mycell-UI/src-tauri/src/api/routes.rs
syoul 95e7cb4bd3 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
2026-04-25 23:02:32 +02:00

69 lines
2.0 KiB
Rust

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,
})
}
}