mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 07:08:05 -05:00
316 lines
10 KiB
Rust
316 lines
10 KiB
Rust
//use darkfi::error::{Error, Result};
|
|
use fxhash::{FxHashMap, FxHashSet};
|
|
use tui::widgets::ListState;
|
|
|
|
use tui::{
|
|
backend::Backend,
|
|
layout::{Constraint, Direction, Layout, Rect},
|
|
style::Style,
|
|
text::{Span, Spans},
|
|
widgets::{Block, Borders, List, ListItem, Paragraph},
|
|
Frame,
|
|
};
|
|
|
|
use darkfi::util::Timestamp;
|
|
|
|
use crate::{
|
|
error::{DnetViewError, DnetViewResult},
|
|
model::{NodeInfo, SelectableObject},
|
|
};
|
|
//use log::debug;
|
|
|
|
#[derive(Debug)]
|
|
pub struct View {
|
|
pub nodes: NodeInfoView,
|
|
pub msg_log: FxHashMap<String, Vec<(Timestamp, String, String)>>,
|
|
pub active_ids: IdListView,
|
|
pub selectables: FxHashMap<String, SelectableObject>,
|
|
}
|
|
|
|
impl View {
|
|
pub fn new(
|
|
nodes: NodeInfoView,
|
|
msg_log: FxHashMap<String, Vec<(Timestamp, String, String)>>,
|
|
active_ids: IdListView,
|
|
selectables: FxHashMap<String, SelectableObject>,
|
|
) -> View {
|
|
View { nodes, msg_log, active_ids, selectables }
|
|
}
|
|
|
|
pub fn update(
|
|
&mut self,
|
|
nodes: FxHashMap<String, NodeInfo>,
|
|
msg_log: FxHashMap<String, Vec<(Timestamp, String, String)>>,
|
|
selectables: FxHashMap<String, SelectableObject>,
|
|
) {
|
|
self.update_nodes(nodes);
|
|
self.update_selectable(selectables);
|
|
self.update_active_ids();
|
|
self.update_msg_log(msg_log);
|
|
}
|
|
|
|
fn update_nodes(&mut self, nodes: FxHashMap<String, NodeInfo>) {
|
|
for (id, node) in nodes {
|
|
self.nodes.infos.insert(id, node);
|
|
}
|
|
}
|
|
|
|
fn update_selectable(&mut self, selectables: FxHashMap<String, SelectableObject>) {
|
|
for (id, obj) in selectables {
|
|
self.selectables.insert(id, obj);
|
|
}
|
|
}
|
|
|
|
fn update_active_ids(&mut self) {
|
|
for info in self.nodes.infos.values() {
|
|
self.active_ids.ids.insert(info.id.to_string());
|
|
for child in &info.children {
|
|
if !child.is_empty == true {
|
|
self.active_ids.ids.insert(child.id.to_string());
|
|
for child in &child.children {
|
|
self.active_ids.ids.insert(child.id.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_msg_log(&mut self, msg_log: FxHashMap<String, Vec<(Timestamp, String, String)>>) {
|
|
for (id, msg) in msg_log {
|
|
self.msg_log.insert(id, msg);
|
|
}
|
|
}
|
|
|
|
pub fn render<B: Backend>(&mut self, f: &mut Frame<'_, B>) -> DnetViewResult<()> {
|
|
let margin = 2;
|
|
let direction = Direction::Horizontal;
|
|
let cnstrnts = vec![Constraint::Percentage(50), Constraint::Percentage(50)];
|
|
|
|
let slice = Layout::default()
|
|
.direction(direction)
|
|
.margin(margin)
|
|
.constraints(cnstrnts)
|
|
.split(f.size());
|
|
|
|
let mut id_list = self.render_id_list(f, slice.clone())?;
|
|
|
|
// remove any duplicates
|
|
id_list.dedup();
|
|
|
|
if id_list.is_empty() {
|
|
// we have not received any data
|
|
Ok(())
|
|
} else {
|
|
// get the id at the current index
|
|
match self.active_ids.state.selected() {
|
|
Some(i) => match id_list.get(i) {
|
|
Some(i) => {
|
|
self.render_info(f, slice.clone(), i.to_string())?;
|
|
Ok(())
|
|
}
|
|
None => return Err(DnetViewError::NoIdAtIndex),
|
|
},
|
|
// nothing is selected right now
|
|
None => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render_id_list<B: Backend>(
|
|
&mut self,
|
|
f: &mut Frame<'_, B>,
|
|
slice: Vec<Rect>,
|
|
) -> DnetViewResult<Vec<String>> {
|
|
let style = Style::default();
|
|
let mut nodes = Vec::new();
|
|
let mut ids: Vec<String> = Vec::new();
|
|
|
|
for info in self.nodes.infos.values() {
|
|
let name_span = Span::raw(&info.name);
|
|
let lines = vec![Spans::from(name_span)];
|
|
let names = ListItem::new(lines);
|
|
nodes.push(names);
|
|
ids.push(info.id.clone());
|
|
for session in &info.children {
|
|
if !session.is_empty == true {
|
|
let name = Span::styled(format!(" {}", session.name), style);
|
|
let lines = vec![Spans::from(name)];
|
|
let names = ListItem::new(lines);
|
|
nodes.push(names);
|
|
ids.push(session.id.clone());
|
|
for connection in &session.children {
|
|
let mut info = Vec::new();
|
|
let name = Span::styled(format!(" {}", connection.addr), style);
|
|
info.push(name);
|
|
match connection.last_status.as_str() {
|
|
"recv" => {
|
|
let msg = Span::styled(
|
|
format!(" [R: {}]", connection.last_msg),
|
|
style,
|
|
);
|
|
info.push(msg);
|
|
}
|
|
"sent" => {
|
|
let msg = Span::styled(
|
|
format!(" [S: {}]", connection.last_msg),
|
|
style,
|
|
);
|
|
info.push(msg);
|
|
}
|
|
"Null" => {
|
|
// Empty msg log. Do nothing
|
|
}
|
|
data => return Err(DnetViewError::UnexpectedData(data.to_string())),
|
|
}
|
|
|
|
let lines = vec![Spans::from(info)];
|
|
let names = ListItem::new(lines);
|
|
nodes.push(names);
|
|
ids.push(connection.id.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let nodes =
|
|
List::new(nodes).block(Block::default().borders(Borders::ALL)).highlight_symbol(">> ");
|
|
|
|
f.render_stateful_widget(nodes, slice[0], &mut self.active_ids.state);
|
|
|
|
Ok(ids)
|
|
}
|
|
|
|
fn render_info<B: Backend>(
|
|
&mut self,
|
|
f: &mut Frame<'_, B>,
|
|
slice: Vec<Rect>,
|
|
selected: String,
|
|
) -> DnetViewResult<()> {
|
|
let style = Style::default();
|
|
let mut lines = Vec::new();
|
|
|
|
if self.selectables.is_empty() {
|
|
// we have not received any selectable data
|
|
return Ok(())
|
|
} else {
|
|
let info = self.selectables.get(&selected);
|
|
|
|
match info {
|
|
Some(SelectableObject::Node(node)) => {
|
|
let node_info =
|
|
Span::styled(format!("External addr: {}", node.external_addr), style);
|
|
lines.push(Spans::from(node_info));
|
|
}
|
|
Some(SelectableObject::Session(session)) => {
|
|
if session.accept_addr.is_some() {
|
|
let session_info = Span::styled(
|
|
format!("Accept addr: {}", session.accept_addr.as_ref().unwrap()),
|
|
style,
|
|
);
|
|
lines.push(Spans::from(session_info));
|
|
}
|
|
}
|
|
Some(SelectableObject::Connect(connect)) => {
|
|
let log = self.msg_log.get(&connect.id);
|
|
match log {
|
|
Some(values) => {
|
|
for (t, k, v) in values {
|
|
lines.push(Spans::from(match k.as_str() {
|
|
"send" => {
|
|
Span::styled(format!("{} S: {}", t, v), style)
|
|
}
|
|
"recv" => {
|
|
Span::styled(format!("{} R: {}", t, v), style)
|
|
}
|
|
data => {
|
|
return Err(DnetViewError::UnexpectedData(data.to_string()))
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
None => return Err(DnetViewError::CannotFindId),
|
|
}
|
|
}
|
|
None => return Err(DnetViewError::NotSelectableObject),
|
|
}
|
|
}
|
|
|
|
let graph = Paragraph::new(lines)
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.style(Style::default());
|
|
|
|
f.render_widget(graph, slice[1]);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct IdListView {
|
|
pub state: ListState,
|
|
pub ids: FxHashSet<String>,
|
|
}
|
|
|
|
impl IdListView {
|
|
pub fn new(ids: FxHashSet<String>) -> IdListView {
|
|
IdListView { state: ListState::default(), ids }
|
|
}
|
|
pub fn next(&mut self) {
|
|
let i = match self.state.selected() {
|
|
Some(i) => {
|
|
if i >= self.ids.len() - 1 {
|
|
0
|
|
} else {
|
|
i + 1
|
|
}
|
|
}
|
|
None => 0,
|
|
};
|
|
self.state.select(Some(i));
|
|
}
|
|
|
|
pub fn previous(&mut self) {
|
|
let i = match self.state.selected() {
|
|
Some(i) => {
|
|
if i == 0 {
|
|
self.ids.len() - 1
|
|
} else {
|
|
i - 1
|
|
}
|
|
}
|
|
None => 0,
|
|
};
|
|
self.state.select(Some(i));
|
|
}
|
|
|
|
pub fn unselect(&mut self) {
|
|
self.state.select(None);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct NodeInfoView {
|
|
pub index: usize,
|
|
pub infos: FxHashMap<String, NodeInfo>,
|
|
}
|
|
|
|
impl NodeInfoView {
|
|
pub fn new(infos: FxHashMap<String, NodeInfo>) -> NodeInfoView {
|
|
let index = 0;
|
|
|
|
NodeInfoView { index, infos }
|
|
}
|
|
|
|
pub fn next(&mut self) {
|
|
self.index = (self.index + 1) % self.infos.len();
|
|
}
|
|
|
|
pub fn previous(&mut self) {
|
|
if self.index > 0 {
|
|
self.index -= 1;
|
|
} else {
|
|
self.index = self.infos.len() - 1;
|
|
}
|
|
}
|
|
}
|