| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- //use log::debug;
- use std::collections::HashMap;
- use ratatui::{
- backend::Backend,
- layout::{Constraint, Direction, Layout, Rect},
- style::{Color, Modifier, Style},
- text::{Line, Span},
- widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
- Frame,
- };
- use darkfi::util::time::NanoTimestamp;
- use crate::{
- error::{DnetViewError, DnetViewResult},
- model::{NodeInfo, SelectableObject},
- };
- type MsgLog = Vec<(NanoTimestamp, String, String)>;
- type MsgMap = HashMap<String, MsgLog>;
- #[derive(Debug, Clone)]
- pub struct View {
- pub id_menu: IdMenu,
- pub msg_list: MsgList,
- pub selectables: HashMap<String, SelectableObject>,
- pub ordered_list: Vec<String>,
- }
- impl Default for View {
- fn default() -> Self {
- Self::new()
- }
- }
- impl<'a> View {
- pub fn new() -> Self {
- let msg_map = HashMap::new();
- let msg_list = MsgList::new(msg_map, 0);
- let selectables = HashMap::new();
- let id_menu = IdMenu::new(Vec::new());
- let ordered_list = Vec::new();
- Self { id_menu, msg_list, selectables, ordered_list }
- }
- pub fn update(&mut self, msg_map: MsgMap, selectables: HashMap<String, SelectableObject>) {
- self.update_selectable(selectables.clone());
- self.update_msg_list(msg_map);
- self.update_id_menu(selectables);
- self.update_msg_index();
- self.make_ordered_list();
- }
- // We copy the values into a string to initialize List as a StatefulObject.
- fn update_id_menu(&mut self, selectables: HashMap<String, SelectableObject>) {
- for id in selectables.keys() {
- if !self.id_menu.ids.iter().any(|i| i == id) {
- self.id_menu.ids.push(id.to_string());
- }
- }
- }
- // We first add every selectable object into a hashmap to avoid duplicates.
- fn update_selectable(&mut self, selectables: HashMap<String, SelectableObject>) {
- for (id, obj) in selectables {
- self.selectables.insert(id, obj);
- }
- }
- // The order of the ordered_list created here must match the order
- // of the Vec<ListItem> created in render_left().
- // This is used to render_right() correctly.
- fn make_ordered_list(&mut self) {
- for obj in self.selectables.values() {
- match obj {
- SelectableObject::Node(node) => {
- if !self.ordered_list.iter().any(|i| i == &node.dnet_id) {
- self.ordered_list.push(node.dnet_id.clone());
- }
- if !node.is_offline && node.dnet_enabled {
- for inbound in &node.inbound {
- if !inbound.is_empty {
- if !self.ordered_list.iter().any(|i| i == &inbound.dnet_id) {
- self.ordered_list.push(inbound.dnet_id.clone());
- }
- if !self.ordered_list.iter().any(|i| i == &inbound.info.dnet_id) {
- self.ordered_list.push(inbound.info.dnet_id.clone());
- }
- }
- }
- for outbound in &node.outbound {
- if !outbound.is_empty {
- if !self.ordered_list.iter().any(|i| i == &outbound.dnet_id) {
- self.ordered_list.push(outbound.dnet_id.clone());
- }
- if !self.ordered_list.iter().any(|i| i == &outbound.info.dnet_id) {
- self.ordered_list.push(outbound.info.dnet_id.clone());
- }
- }
- }
- }
- }
- SelectableObject::Lilith(lilith) => {
- if !self.ordered_list.iter().any(|i| i == &lilith.id) {
- self.ordered_list.push(lilith.id.clone());
- }
- for network in &lilith.networks {
- if !self.ordered_list.iter().any(|i| i == &network.id) {
- self.ordered_list.push(network.id.clone());
- }
- }
- }
- _ => (),
- }
- }
- }
- // TODO: this function displays msgs according to what id is
- // selected. It's ugly, would prefer something more simple.
- fn update_msg_index(&mut self) {
- if let Some(sel) = self.id_menu.state.selected() {
- if let Some(ord) = self.ordered_list.get(sel) {
- if let Some(i) = self.msg_list.msg_map.get(ord) {
- self.msg_list.index = i.len();
- }
- }
- }
- }
- fn update_msg_list(&mut self, msg_map: MsgMap) {
- for (id, msg) in msg_map {
- self.msg_list.msg_map.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());
- self.render_left(f, slice[0])?;
- if self.ordered_list.is_empty() {
- // we have not received any data
- Ok(())
- } else {
- // get the id at the current index
- match self.id_menu.state.selected() {
- Some(i) => match self.ordered_list.get(i) {
- Some(i) => {
- let id = i.clone();
- self.render_right(f, slice[1], id)?;
- Ok(())
- }
- None => Err(DnetViewError::NoIdAtIndex),
- },
- // nothing is selected right now
- None => Ok(()),
- }
- }
- }
- fn render_left<B: Backend>(&mut self, f: &mut Frame<'_, B>, slice: Rect) -> DnetViewResult<()> {
- let style = Style::default();
- let mut nodes = Vec::new();
- for obj in self.selectables.values() {
- match obj {
- SelectableObject::Node(node) => {
- if node.is_offline {
- let style =
- Style::default().fg(Color::LightBlue).add_modifier(Modifier::ITALIC);
- let mut name = String::new();
- name.push_str(&node.name);
- name.push_str("(Offline)");
- let name_span = Span::styled(name, style);
- let lines = vec![Line::from(name_span)];
- let names = ListItem::new(lines);
- nodes.push(names);
- } else {
- if !node.dnet_enabled {
- let style =
- Style::default().fg(Color::LightBlue).add_modifier(Modifier::BOLD);
- let mut name = String::new();
- name.push_str(&node.name);
- name.push_str("(dnetview is not enabled)");
- let name_span = Span::styled(name, style);
- let lines = vec![Line::from(name_span)];
- let names = ListItem::new(lines);
- nodes.push(names);
- } else {
- let name_span = Span::raw(&node.name);
- let lines = vec![Line::from(name_span)];
- let names = ListItem::new(lines);
- nodes.push(names);
- if !node.inbound.is_empty() {
- let name = Span::styled(format!(" Inbound"), style);
- let lines = vec![Line::from(name)];
- let names = ListItem::new(lines);
- nodes.push(names);
- for inbound in &node.inbound {
- let mut infos = Vec::new();
- match inbound.info.addr.as_str() {
- "Null" => {
- let style = Style::default()
- .fg(Color::Blue)
- .add_modifier(Modifier::ITALIC);
- let name = Span::styled(
- format!(" {} ", inbound.info.addr),
- style,
- );
- infos.push(name);
- }
- addr => {
- let name =
- Span::styled(format!(" {}", addr), style);
- infos.push(name);
- if !inbound.info.remote_id.is_empty() {
- let remote_id = Span::styled(
- format!("({})", inbound.info.remote_id),
- style,
- );
- infos.push(remote_id)
- }
- }
- }
- let lines = vec![Line::from(infos)];
- let names = ListItem::new(lines);
- nodes.push(names);
- }
- }
- if !&node.outbound.is_empty() {
- let name = Span::styled(format!(" Outbound"), style);
- let lines = vec![Line::from(name)];
- let names = ListItem::new(lines);
- nodes.push(names);
- for outbound in &node.outbound {
- let mut infos = Vec::new();
- match outbound.info.addr.as_str() {
- "Null" => {
- let style = Style::default()
- .fg(Color::Blue)
- .add_modifier(Modifier::ITALIC);
- let name = Span::styled(
- format!(" {} ", outbound.info.addr),
- style,
- );
- infos.push(name);
- }
- addr => {
- let name =
- Span::styled(format!(" {}", addr), style);
- infos.push(name);
- if !outbound.info.remote_id.is_empty() {
- let remote_id = Span::styled(
- format!("({})", outbound.info.remote_id),
- style,
- );
- infos.push(remote_id)
- }
- }
- }
- let lines = vec![Line::from(infos)];
- let names = ListItem::new(lines);
- nodes.push(names);
- }
- }
- }
- }
- }
- SelectableObject::Lilith(lilith) => {
- let name_span = Span::raw(&lilith.name);
- let lines = vec![Line::from(name_span)];
- let names = ListItem::new(lines);
- nodes.push(names);
- for network in &lilith.networks {
- let name = Span::styled(format!(" {}", network.name), style);
- let lines = vec![Line::from(name)];
- let names = ListItem::new(lines);
- nodes.push(names);
- }
- }
- _ => (),
- }
- }
- let nodes =
- List::new(nodes).block(Block::default().borders(Borders::ALL)).highlight_symbol(">> ");
- f.render_stateful_widget(nodes, slice, &mut self.id_menu.state);
- Ok(())
- }
- fn parse_msg_list(&self, info_id: String) -> DnetViewResult<List<'a>> {
- let send_style = Style::default().fg(Color::LightCyan);
- let recv_style = Style::default().fg(Color::DarkGray);
- let mut texts = Vec::new();
- let mut lines = Vec::new();
- let log = self.msg_list.msg_map.get(&info_id);
- match log {
- Some(values) => {
- for (i, (t, k, v)) in values.iter().enumerate() {
- lines.push(match k.as_str() {
- "send" => {
- Span::styled(format!("{} {} S: {}", i, t, v), send_style)
- }
- "recv" => {
- Span::styled(format!("{} {} R: {}", i, t, v), recv_style)
- }
- data => return Err(DnetViewError::UnexpectedData(data.to_string())),
- });
- }
- }
- None => return Err(DnetViewError::CannotFindId),
- }
- for line in lines.clone() {
- let text = ListItem::new(line);
- texts.push(text);
- }
- let msg_list = List::new(texts).block(Block::default().borders(Borders::ALL));
- Ok(msg_list)
- }
- fn render_right<B: Backend>(
- &mut self,
- f: &mut Frame<'_, B>,
- slice: Rect,
- selected: String,
- ) -> DnetViewResult<()> {
- //debug!(target: "dnetview", "render_right() selected ID: {}", selected.clone());
- 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)) => {
- lines.push(Line::from(Span::styled("Type: Normal", style)));
- lines.push(Line::from(Span::styled("Hosts:", style)));
- for host in &node.hosts {
- lines.push(Line::from(Span::styled(format!(" {}", host), style)));
- }
- }
- Some(SelectableObject::Session(session)) => {
- let addr = Span::styled(format!("Addr: {}", session.addr), style);
- lines.push(Line::from(addr));
- if session.state.is_some() {
- let addr = Span::styled(
- format!("State: {}", session.state.as_ref().unwrap()),
- style,
- );
- lines.push(Line::from(addr));
- }
- }
- Some(SelectableObject::Slot(slot)) => {
- let text = self.parse_msg_list(slot.dnet_id.clone())?;
- f.render_stateful_widget(text, slice, &mut self.msg_list.state);
- }
- Some(SelectableObject::Lilith(_lilith)) => {
- lines.push(Line::from(Span::styled("Type: Lilith", style)));
- }
- Some(SelectableObject::Network(network)) => {
- lines.push(Line::from(Span::styled("URLs:", style)));
- for url in &network.urls {
- lines.push(Line::from(Span::styled(format!(" {}", url), style)));
- }
- lines.push(Line::from(Span::styled("Hosts:", style)));
- for node in &network.nodes {
- lines.push(Line::from(Span::styled(format!(" {}", node), style)));
- }
- }
- None => return Err(DnetViewError::NotSelectableObject),
- }
- }
- let graph = Paragraph::new(lines)
- .block(Block::default().borders(Borders::ALL))
- .style(Style::default());
- f.render_widget(graph, slice);
- Ok(())
- }
- }
- #[derive(Debug, Clone)]
- pub struct IdMenu {
- pub state: ListState,
- pub ids: Vec<String>,
- }
- impl IdMenu {
- pub fn new(ids: Vec<String>) -> IdMenu {
- IdMenu { 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 MsgList {
- pub state: ListState,
- pub msg_map: MsgMap,
- pub index: usize,
- }
- impl MsgList {
- pub fn new(msg_map: MsgMap, index: usize) -> MsgList {
- MsgList { state: ListState::default(), msg_map, index }
- }
- // TODO: reimplement
- //pub fn next(&mut self) {
- // let i = match self.state.selected() {
- // Some(i) => {
- // if i >= self.msg_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.msg_len - 1
- // } else {
- // i - 1
- // }
- // }
- // None => 0,
- // };
- // self.state.select(Some(i));
- //}
- pub fn scroll(&mut self) -> DnetViewResult<()> {
- let i = match self.state.selected() {
- Some(i) => i + self.index,
- None => 0,
- };
- self.state.select(Some(i));
- Ok(())
- }
- pub fn unselect(&mut self) {
- self.state.select(None);
- }
- }
- #[derive(Debug, Clone)]
- pub struct NodeInfoView {
- pub index: usize,
- pub infos: HashMap<String, NodeInfo>,
- }
- impl NodeInfoView {
- pub fn new(infos: HashMap<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;
- // }
- //}
- }
|