mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-12 09:33:23 +00:00
Add rustfmt.toml with stable defaults and apply cargo fmt to all source files. This establishes a consistent formatting baseline for CI enforcement.
247 lines
8.2 KiB
Rust
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);
|
|
}
|