Files
fips/src/bin/fipstop/ui/tree.rs
T
Johnathan Corgan 13c0b70dc3 Add rustfmt formatting policy and reformat codebase
Add rustfmt.toml with stable defaults and apply cargo fmt to all
source files. This establishes a consistent formatting baseline
for CI enforcement.
2026-04-10 08:27:07 +00:00

247 lines
8.2 KiB
Rust

use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph};
use crate::app::{App, Tab};
use super::helpers;
pub fn draw(frame: &mut Frame, app: &App, area: Rect) {
let data = match app.data.get(&Tab::Tree) {
Some(d) => d,
None => {
let msg =
Paragraph::new(" Waiting for data...").style(Style::default().fg(Color::DarkGray));
frame.render_widget(msg, area);
return;
}
};
let chunks = Layout::vertical([
Constraint::Length(10), // Tree Position
Constraint::Length(22), // Tree Announce Stats
Constraint::Min(3), // Tree Peers
])
.split(area);
draw_position(frame, data, chunks[0]);
draw_stats(frame, data, chunks[1]);
draw_peers(frame, data, chunks[2]);
}
fn draw_position(frame: &mut Frame, data: &serde_json::Value, area: Rect) {
let root_hex = helpers::str_field(data, "root");
let is_root = data
.get("is_root")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let depth = helpers::u64_field(data, "depth");
let parent_name = helpers::str_field(data, "parent_display_name");
let decl_seq = helpers::u64_field(data, "declaration_sequence");
let decl_signed = helpers::bool_field(data, "declaration_signed");
let root_display = if is_root {
format!("{} (self)", helpers::truncate_hex(root_hex, 16))
} else {
helpers::truncate_hex(root_hex, 16)
};
let parent_display = if is_root {
"self (root)".to_string()
} else {
parent_name.to_string()
};
let mut lines = vec![
helpers::kv_line("Root", &root_display),
helpers::kv_line("Depth", &depth),
helpers::kv_line("Parent", &parent_display),
helpers::kv_line("Declaration", &format!("seq {decl_seq}, {decl_signed}")),
Line::from(""),
];
// Coordinate path: my_coords array is self→root, render root→self
if let Some(coords) = data.get("my_coords").and_then(|v| v.as_array()) {
let mut path_parts: Vec<Span> = vec![Span::styled(
" Path: ",
Style::default().fg(Color::DarkGray),
)];
if coords.is_empty() {
path_parts.push(Span::styled("[root]", Style::default().fg(Color::Yellow)));
} else {
// Reverse: root first, self last
for (i, entry) in coords.iter().rev().enumerate() {
if i > 0 {
path_parts.push(Span::styled(" > ", Style::default().fg(Color::DarkGray)));
}
let hex = entry.as_str().unwrap_or("-");
let color = if i == 0 {
Color::Yellow // root
} else {
Color::White
};
path_parts.push(Span::styled(
helpers::truncate_hex(hex, 8),
Style::default().fg(color),
));
}
path_parts.push(Span::styled(" > ", Style::default().fg(Color::DarkGray)));
path_parts.push(Span::styled(
"[self]",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
));
}
lines.push(Line::from(path_parts));
}
let block = Block::default()
.borders(Borders::ALL)
.title(" Tree Position ");
let inner = block.inner(area);
frame.render_widget(block, area);
frame.render_widget(Paragraph::new(lines), inner);
}
fn draw_stats(frame: &mut Frame, data: &serde_json::Value, area: Rect) {
let block = Block::default()
.borders(Borders::ALL)
.title(" Tree Announce Stats ");
let inner = block.inner(area);
frame.render_widget(block, area);
let mut lines = vec![
helpers::section_header("Inbound"),
helpers::kv_line("Received", &helpers::nested_u64(data, "stats", "received")),
helpers::kv_line("Accepted", &helpers::nested_u64(data, "stats", "accepted")),
helpers::kv_line(
"Decode Error",
&helpers::nested_u64(data, "stats", "decode_error"),
),
helpers::kv_line(
"Unknown Peer",
&helpers::nested_u64(data, "stats", "unknown_peer"),
),
helpers::kv_line(
"Addr Mismatch",
&helpers::nested_u64(data, "stats", "addr_mismatch"),
),
helpers::kv_line(
"Sig Failed",
&helpers::nested_u64(data, "stats", "sig_failed"),
),
helpers::kv_line("Stale", &helpers::nested_u64(data, "stats", "stale")),
helpers::kv_line(
"Parent Switched",
&helpers::nested_u64(data, "stats", "parent_switched"),
),
helpers::kv_line(
"Loop Detected",
&helpers::nested_u64(data, "stats", "loop_detected"),
),
helpers::kv_line(
"Ancestry Changed",
&helpers::nested_u64(data, "stats", "ancestry_changed"),
),
Line::from(""),
helpers::section_header("Outbound"),
helpers::kv_line("Sent", &helpers::nested_u64(data, "stats", "sent")),
helpers::kv_line(
"Rate Limited",
&helpers::nested_u64(data, "stats", "rate_limited"),
),
helpers::kv_line(
"Send Failed",
&helpers::nested_u64(data, "stats", "send_failed"),
),
Line::from(""),
helpers::section_header("Cumulative"),
helpers::kv_line(
"Parent Switches",
&helpers::nested_u64(data, "stats", "parent_switches"),
),
helpers::kv_line(
"Parent Losses",
&helpers::nested_u64(data, "stats", "parent_losses"),
),
helpers::kv_line(
"Flap Dampened",
&helpers::nested_u64(data, "stats", "flap_dampened"),
),
];
// Trim to fit available height
let max_lines = inner.height as usize;
lines.truncate(max_lines);
frame.render_widget(Paragraph::new(lines), inner);
}
fn draw_peers(frame: &mut Frame, data: &serde_json::Value, area: Rect) {
let peers = data
.get("peers")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let my_root = helpers::str_field(data, "root");
let count = peers.len();
let block = Block::default()
.borders(Borders::ALL)
.title(format!(" Tree Peers ({count}) "));
let inner = block.inner(area);
frame.render_widget(block, area);
if peers.is_empty() {
let msg = Paragraph::new(" No peers").style(Style::default().fg(Color::DarkGray));
frame.render_widget(msg, inner);
return;
}
let lines: Vec<Line> = peers
.iter()
.map(|p| {
let name = helpers::str_field(p, "display_name");
let has_depth = p.get("depth").is_some();
if !has_depth {
return Line::from(vec![
Span::styled(
format!(" {name:<16}"),
Style::default().fg(Color::DarkGray),
),
Span::styled("(no position)", Style::default().fg(Color::DarkGray)),
]);
}
let depth = helpers::u64_field(p, "depth");
let dist = helpers::u64_field(p, "distance_to_us");
let peer_root = helpers::str_field(p, "root");
let (root_ind, root_color) = if peer_root == my_root {
("same root", Color::Green)
} else {
("diff root", Color::Red)
};
Line::from(vec![
Span::styled(
format!(" {name:<16}"),
Style::default().add_modifier(Modifier::BOLD),
),
Span::styled("depth: ", Style::default().fg(Color::DarkGray)),
Span::raw(format!("{depth:<4}")),
Span::styled("dist: ", Style::default().fg(Color::DarkGray)),
Span::raw(format!("{dist:<4}")),
Span::styled(root_ind, Style::default().fg(root_color)),
])
})
.collect();
frame.render_widget(Paragraph::new(lines), inner);
}