2026-06-09 09:29:07 +00:00
|
|
|
//! Serializable shape of a node's observable distribution state.
|
2026-02-12 09:13:50 +00:00
|
|
|
//!
|
2026-06-09 09:29:07 +00:00
|
|
|
//! This is the JSON contract the Distribution page renders. The node no longer
|
|
|
|
|
//! *collects* it by polling — per-node telemetry now flows over the datastream,
|
|
|
|
|
//! and the dashboard's datastream consumer reconstructs this exact field shape
|
|
|
|
|
//! from the `membership` / `identity` / `dist.state` channels. The type is
|
|
|
|
|
//! retained as the shared wire shape so external consumers (the example
|
|
|
|
|
//! clusters, the docker integration tests) can deserialize a node's
|
|
|
|
|
//! `/api/distribution` response, and so a producer that builds the shape
|
|
|
|
|
//! directly (the example apps) has one definition to target.
|
2026-02-12 09:13:50 +00:00
|
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
2026-06-09 09:29:07 +00:00
|
|
|
use crate::types::NodeId;
|
2026-02-12 09:13:50 +00:00
|
|
|
|
|
|
|
|
/// Snapshot of a single member in the SWIM membership list.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct MemberInfo {
|
|
|
|
|
pub node_id: String,
|
2026-02-15 09:47:05 +00:00
|
|
|
pub addr: Option<String>,
|
2026-02-12 09:13:50 +00:00
|
|
|
pub state: String,
|
|
|
|
|
pub incarnation: u64,
|
2026-02-19 14:39:33 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub is_authorized: Option<bool>,
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub label: Option<String>,
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub relay_url: Option<String>,
|
2026-02-25 11:11:03 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
|
|
|
pub node_name: Option<String>,
|
2026-06-21 19:54:41 +00:00
|
|
|
/// Cause of this member's most recent SWIM liveness transition (the
|
|
|
|
|
/// production observer's reason string, e.g. "ping-received",
|
|
|
|
|
/// "probe-timeout"). `None` until a transition has been observed; the
|
|
|
|
|
/// old state-diff path could never carry this.
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
|
|
|
pub reason: Option<String>,
|
2026-02-12 09:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Snapshot of a single LRU cache entry.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct CacheEntryInfo {
|
|
|
|
|
pub actor_addr: String,
|
|
|
|
|
pub node_id: String,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-13 07:42:44 +00:00
|
|
|
/// Snapshot of a single registry entry.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct RegistryEntryInfo {
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub actor_addr: String,
|
|
|
|
|
pub node_id: String,
|
|
|
|
|
pub tombstone: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 11:11:03 +00:00
|
|
|
/// Snapshot of a join attempt's real-time status.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct JoinStatusInfo {
|
|
|
|
|
pub node_id: String,
|
|
|
|
|
/// "connecting", "sending", "sent", "failed"
|
|
|
|
|
pub phase: String,
|
|
|
|
|
/// E.g. "2/5" for attempt progress, or error message for failed
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub detail: Option<String>,
|
|
|
|
|
pub has_relay: bool,
|
|
|
|
|
pub has_direct: bool,
|
|
|
|
|
pub direct_addr_count: usize,
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 09:29:07 +00:00
|
|
|
/// Complete shape of a node's observable distribution state.
|
2026-02-12 09:13:50 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct DistributionNodeSnapshot {
|
|
|
|
|
/// This node's ID (hex-encoded).
|
|
|
|
|
pub node_id: String,
|
2026-02-15 09:47:05 +00:00
|
|
|
/// This node's listen address (filled by driver, None for protocol-only snapshots).
|
|
|
|
|
pub listen_addr: Option<String>,
|
2026-02-12 09:13:50 +00:00
|
|
|
|
|
|
|
|
// ─── SWIM membership ─────────────────────────────────────────────
|
|
|
|
|
/// All known members with their state.
|
|
|
|
|
pub members: Vec<MemberInfo>,
|
|
|
|
|
/// Count of alive members.
|
|
|
|
|
pub alive_count: usize,
|
|
|
|
|
/// Count of suspected members.
|
|
|
|
|
pub suspect_count: usize,
|
|
|
|
|
/// Count of dead members.
|
|
|
|
|
pub dead_count: usize,
|
|
|
|
|
|
|
|
|
|
// ─── Location cache ──────────────────────────────────────────────
|
|
|
|
|
/// Number of entries in the LRU cache.
|
|
|
|
|
pub cache_size: usize,
|
|
|
|
|
/// All cache entries (actor → node).
|
|
|
|
|
pub cache_entries: Vec<CacheEntryInfo>,
|
|
|
|
|
|
2026-06-09 09:29:07 +00:00
|
|
|
// ─── Directory ───────────────────────────────────────────────────
|
|
|
|
|
/// Number of actor→host routes this node currently knows (the converged
|
|
|
|
|
/// directory `RouteView` size).
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub directory_route_count: usize,
|
2026-02-12 09:13:50 +00:00
|
|
|
|
2026-02-13 07:42:44 +00:00
|
|
|
// ─── Registry ────────────────────────────────────────────────────
|
|
|
|
|
/// Number of entries in the cluster registry (including tombstones).
|
|
|
|
|
pub registry_size: usize,
|
|
|
|
|
/// Number of tombstoned entries.
|
|
|
|
|
pub registry_tombstones: usize,
|
|
|
|
|
/// All registry entries.
|
|
|
|
|
pub registry_entries: Vec<RegistryEntryInfo>,
|
|
|
|
|
|
2026-02-12 09:13:50 +00:00
|
|
|
// ─── Gossip pairs ────────────────────────────────────────────────
|
|
|
|
|
/// Recent SWIM probe targets (most recent last).
|
|
|
|
|
pub recent_probe_targets: Vec<String>,
|
2026-02-19 14:39:33 +00:00
|
|
|
|
|
|
|
|
// ─── Peer auth ──────────────────────────────────────────────────
|
|
|
|
|
/// "open" or "allow-list".
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub peer_auth_mode: String,
|
|
|
|
|
/// Number of authorized peers (None if open mode).
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub authorized_peer_count: Option<usize>,
|
|
|
|
|
|
|
|
|
|
/// Human-readable node name (e.g. "swift-falcon").
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
|
|
|
pub node_name: Option<String>,
|
|
|
|
|
|
|
|
|
|
/// Base58-encoded invite code for this node.
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
|
|
|
pub invite_code: Option<String>,
|
|
|
|
|
|
|
|
|
|
/// This node's relay URL, if running an embedded relay server.
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
|
|
|
pub relay_url: Option<String>,
|
2026-02-25 11:11:03 +00:00
|
|
|
|
|
|
|
|
/// Build version string (e.g. "branch @ hash").
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
|
|
|
pub version: Option<String>,
|
|
|
|
|
|
|
|
|
|
/// Real-time join statuses for peers being connected to.
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
|
pub join_statuses: Vec<JoinStatusInfo>,
|
2026-02-12 09:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn node_id_hex(id: &NodeId) -> String {
|
|
|
|
|
id.0.iter().map(|b| format!("{:02x}", b)).collect()
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 09:29:07 +00:00
|
|
|
impl DistributionNodeSnapshot {
|
|
|
|
|
/// An empty shape for `node_id`, with every protocol field zeroed/empty.
|
|
|
|
|
pub fn empty(node_id: NodeId) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
node_id: node_id_hex(&node_id),
|
2026-02-15 09:47:05 +00:00
|
|
|
listen_addr: None,
|
2026-06-09 09:29:07 +00:00
|
|
|
members: Vec::new(),
|
|
|
|
|
alive_count: 0,
|
|
|
|
|
suspect_count: 0,
|
|
|
|
|
dead_count: 0,
|
|
|
|
|
cache_size: 0,
|
|
|
|
|
cache_entries: Vec::new(),
|
|
|
|
|
directory_route_count: 0,
|
|
|
|
|
registry_size: 0,
|
|
|
|
|
registry_tombstones: 0,
|
|
|
|
|
registry_entries: Vec::new(),
|
|
|
|
|
recent_probe_targets: Vec::new(),
|
2026-02-19 14:39:33 +00:00
|
|
|
peer_auth_mode: "open".into(),
|
|
|
|
|
authorized_peer_count: None,
|
2026-06-09 09:29:07 +00:00
|
|
|
node_name: None,
|
2026-02-19 14:39:33 +00:00
|
|
|
invite_code: None,
|
2026-06-09 09:29:07 +00:00
|
|
|
relay_url: None,
|
2026-02-25 11:11:03 +00:00
|
|
|
version: None,
|
|
|
|
|
join_statuses: Vec::new(),
|
2026-02-12 09:13:50 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|