use net_sdk::deck::{MeshOsSnapshot, PeerSnapshot};
use net_sdk::subnets::SubnetId;
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::Modifier,
text::{Line, Span},
widgets::{Block, Borders, Paragraph, Wrap},
Frame,
};
use crate::{theme, widgets};
#[derive(Clone, Debug)]
pub struct SubnetFocusEntry {
pub subnet: SubnetId,
pub members: Vec<u64>,
pub is_local: bool,
pub member_cursor: usize,
}
pub struct LocalMemberRow<'a> {
pub id: u64,
pub peer: &'a PeerSnapshot,
pub local_maintenance: &'a net_sdk::deck::MaintenanceStateSnapshot,
}
pub fn render(
frame: &mut Frame<'_>,
area: Rect,
focus: &SubnetFocusEntry,
snapshot: &MeshOsSnapshot,
local: Option<LocalMemberRow<'_>>,
) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(7), Constraint::Min(0)])
.split(area);
render_header(frame, chunks[0], focus, snapshot);
render_members(frame, chunks[1], focus, snapshot, local);
}
fn render_header(
frame: &mut Frame<'_>,
area: Rect,
focus: &SubnetFocusEntry,
snapshot: &MeshOsSnapshot,
) {
let title = Line::from(vec![
Span::styled(format!("{} ", theme::SECTION_PREFIX), theme::green()),
Span::styled("SUBNET", theme::green_hi()),
Span::styled(" ", theme::chrome()),
Span::styled(
focus.subnet.to_string(),
theme::text().add_modifier(Modifier::BOLD),
),
]);
let block = Block::default()
.borders(Borders::ALL)
.border_style(theme::rule())
.title(title);
let inner = block.inner(area);
frame.render_widget(block, area);
let depth = focus.subnet.depth();
let parent_str = if depth == 0 {
"—".to_string()
} else {
focus.subnet.parent().to_string()
};
let local_str = if focus.is_local { "yes" } else { "no" };
let local_style = if focus.is_local {
theme::green()
} else {
theme::dim()
};
let (health_text, health_style) = super::subnets::health_rollup(&focus.members, snapshot);
let lines = vec![
Line::from(vec![
Span::styled(" depth: ", theme::chrome()),
Span::styled(format!("{depth}"), theme::text()),
Span::styled(" parent: ", theme::chrome()),
Span::styled(parent_str, theme::text()),
]),
Line::from(vec![
Span::styled(" members: ", theme::chrome()),
Span::styled(format!("{}", focus.members.len()), theme::text()),
Span::styled(" local: ", theme::chrome()),
Span::styled(local_str, local_style),
]),
Line::from(vec![
Span::styled(" health: ", theme::chrome()),
Span::styled(health_text, health_style),
]),
];
frame.render_widget(
Paragraph::new(lines)
.alignment(Alignment::Left)
.wrap(Wrap { trim: false }),
inner,
);
}
fn render_members(
frame: &mut Frame<'_>,
area: Rect,
focus: &SubnetFocusEntry,
snapshot: &MeshOsSnapshot,
local: Option<LocalMemberRow<'_>>,
) {
if focus.members.is_empty() {
let block = Block::default()
.borders(Borders::ALL)
.border_style(theme::rule())
.title(widgets::section_title("MEMBERS", "0 nodes"));
let inner = block.inner(area);
frame.render_widget(block, area);
widgets::empty::render(
frame,
inner,
"no peers in this subnet",
"the subnet appears in the snapshot but no node id was tagged with it",
);
return;
}
let local_id = local.as_ref().map(|r| r.id);
let mut nodes_iter: Vec<(u64, &PeerSnapshot)> = Vec::with_capacity(focus.members.len());
for id in &focus.members {
if is_visible_member(*id, local_id, snapshot) {
if Some(*id) == local_id {
if let Some(r) = local.as_ref() {
nodes_iter.push((*id, r.peer));
}
} else if let Some(p) = snapshot.peers.get(id) {
nodes_iter.push((*id, p));
}
}
}
if nodes_iter.is_empty() {
let block = Block::default()
.borders(Borders::ALL)
.border_style(theme::rule())
.title(widgets::section_title(
"MEMBERS",
&format!("{} not in snapshot", focus.members.len()),
));
let inner = block.inner(area);
frame.render_widget(block, area);
widgets::empty::render(
frame,
inner,
"subnet members are tagged but absent from the snapshot",
"common under demo fixtures or when the snapshot hasn't seen those peers yet",
);
return;
}
let cursor = focus.member_cursor.min(nodes_iter.len() - 1);
let pos = cursor + 1;
let title_spans = vec![
Span::styled(format!("{} ", theme::SECTION_PREFIX), theme::green()),
Span::styled("MEMBERS", theme::green_hi()),
Span::styled(
format!(
" {} of {} in snapshot",
nodes_iter.len(),
focus.members.len()
),
theme::chrome(),
),
Span::styled(format!(" {pos}/{}", nodes_iter.len()), theme::dim()),
];
let local_maintenance_mirror = local
.as_ref()
.map(|r| super::nodes::local_maintenance_mirror(r.local_maintenance));
super::nodes::render_nodes_view(
frame,
area,
title_spans,
&nodes_iter,
snapshot,
cursor,
local_id,
local_maintenance_mirror,
);
}
fn is_visible_member(id: u64, local_id: Option<u64>, snapshot: &MeshOsSnapshot) -> bool {
Some(id) == local_id || snapshot.peers.contains_key(&id)
}
pub fn cursored_member_id(
focus: &SubnetFocusEntry,
snapshot: &MeshOsSnapshot,
local_id: Option<u64>,
) -> Option<u64> {
let visible: Vec<u64> = focus
.members
.iter()
.copied()
.filter(|id| is_visible_member(*id, local_id, snapshot))
.collect();
let idx = focus.member_cursor.min(visible.len().saturating_sub(1));
visible.get(idx).copied()
}
pub fn visible_member_count(
focus: &SubnetFocusEntry,
snapshot: &MeshOsSnapshot,
local_id: Option<u64>,
) -> usize {
focus
.members
.iter()
.filter(|id| is_visible_member(**id, local_id, snapshot))
.count()
}