view.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. use fxhash::FxHashMap;
  2. use tui::widgets::ListState;
  3. use tui::{
  4. backend::Backend,
  5. layout::{Constraint, Direction, Layout, Rect},
  6. style::{Color, Modifier, Style},
  7. text::{Span, Spans},
  8. widgets::{Block, Borders, List, ListItem, Paragraph},
  9. Frame,
  10. };
  11. use darkfi::util::NanoTimestamp;
  12. use crate::{
  13. error::{DnetViewError, DnetViewResult},
  14. model::{NodeInfo, SelectableObject},
  15. };
  16. //use log::debug;
  17. type MsgLog = Vec<(NanoTimestamp, String, String)>;
  18. type MsgMap = FxHashMap<String, MsgLog>;
  19. #[derive(Debug, Clone)]
  20. pub struct View {
  21. pub id_menu: IdMenu,
  22. pub msg_list: MsgList,
  23. pub selectables: FxHashMap<String, SelectableObject>,
  24. }
  25. impl<'a> View {
  26. pub fn new() -> View {
  27. let msg_map = FxHashMap::default();
  28. let msg_list = MsgList::new(msg_map.clone(), 0);
  29. let selectables = FxHashMap::default();
  30. let id_menu = IdMenu::new(Vec::new());
  31. View { id_menu, msg_list, selectables }
  32. }
  33. pub fn update(
  34. &mut self,
  35. ids: Vec<String>,
  36. msg_map: MsgMap,
  37. selectables: FxHashMap<String, SelectableObject>,
  38. ) {
  39. self.update_selectable(selectables);
  40. self.update_msg_list(msg_map);
  41. self.update_id_menu(ids);
  42. self.update_msg_index();
  43. }
  44. fn update_id_menu(&mut self, ids: Vec<String>) {
  45. for id in ids {
  46. if !self.id_menu.ids.iter().any(|i| i == &id) {
  47. self.id_menu.ids.push(id);
  48. }
  49. }
  50. }
  51. fn update_selectable(&mut self, selectables: FxHashMap<String, SelectableObject>) {
  52. for (id, obj) in selectables {
  53. self.selectables.insert(id, obj);
  54. }
  55. }
  56. // TODO: this function is dynamically resizing the msgs index
  57. // according to what set of msgs is selected.
  58. // it's ugly. would prefer something more simple
  59. fn update_msg_index(&mut self) {
  60. match self.id_menu.state.selected() {
  61. Some(i) => match self.id_menu.ids.get(i) {
  62. Some(i) => match self.msg_list.msg_map.get(i) {
  63. Some(i) => {
  64. self.msg_list.index = i.len();
  65. }
  66. None => {}
  67. },
  68. None => {}
  69. },
  70. None => {}
  71. }
  72. }
  73. fn update_msg_list(&mut self, msg_map: MsgMap) {
  74. for (id, msg) in msg_map {
  75. self.msg_list.msg_map.insert(id, msg);
  76. }
  77. }
  78. pub fn render<B: Backend>(&mut self, f: &mut Frame<'_, B>) -> DnetViewResult<()> {
  79. let margin = 2;
  80. let direction = Direction::Horizontal;
  81. let cnstrnts = vec![Constraint::Percentage(50), Constraint::Percentage(50)];
  82. let slice = Layout::default()
  83. .direction(direction)
  84. .margin(margin)
  85. .constraints(cnstrnts)
  86. .split(f.size());
  87. self.render_ids(f, slice.clone())?;
  88. if self.id_menu.ids.is_empty() {
  89. // we have not received any data
  90. Ok(())
  91. } else {
  92. // get the id at the current index
  93. match self.id_menu.state.selected() {
  94. Some(i) => match self.id_menu.ids.get(i) {
  95. Some(i) => {
  96. let id = i.clone();
  97. self.render_info(f, slice, id)?;
  98. Ok(())
  99. }
  100. None => Err(DnetViewError::NoIdAtIndex),
  101. },
  102. // nothing is selected right now
  103. None => Ok(()),
  104. }
  105. }
  106. }
  107. fn render_ids<B: Backend>(
  108. &mut self,
  109. f: &mut Frame<'_, B>,
  110. slice: Vec<Rect>,
  111. ) -> DnetViewResult<()> {
  112. let style = Style::default();
  113. let mut nodes = Vec::new();
  114. for obj in self.selectables.values() {
  115. match obj {
  116. SelectableObject::Node(info) => match info.is_offline {
  117. true => {
  118. let style = Style::default().fg(Color::Blue).add_modifier(Modifier::ITALIC);
  119. let mut name = String::new();
  120. name.push_str(&info.name);
  121. name.push_str("(Offline)");
  122. let name_span = Span::styled(name, style);
  123. let lines = vec![Spans::from(name_span)];
  124. let names = ListItem::new(lines);
  125. nodes.push(names);
  126. }
  127. false => {
  128. let name_span = Span::raw(&info.name);
  129. let lines = vec![Spans::from(name_span)];
  130. let names = ListItem::new(lines);
  131. nodes.push(names);
  132. for session in &info.children {
  133. if !session.is_empty {
  134. let name = Span::styled(format!(" {}", session.name), style);
  135. let lines = vec![Spans::from(name)];
  136. let names = ListItem::new(lines);
  137. nodes.push(names);
  138. for connection in &session.children {
  139. let mut info = Vec::new();
  140. match connection.addr.as_str() {
  141. "Null" => {
  142. let style = Style::default()
  143. .fg(Color::Blue)
  144. .add_modifier(Modifier::ITALIC);
  145. let name = Span::styled(
  146. format!(" {} ", connection.addr),
  147. style,
  148. );
  149. info.push(name);
  150. }
  151. addr => {
  152. let name = Span::styled(
  153. format!(
  154. " {} ({})",
  155. addr, connection.remote_node_id
  156. ),
  157. style,
  158. );
  159. info.push(name);
  160. }
  161. }
  162. let lines = vec![Spans::from(info)];
  163. let names = ListItem::new(lines);
  164. nodes.push(names);
  165. }
  166. }
  167. }
  168. }
  169. },
  170. _ => {}
  171. }
  172. }
  173. let nodes =
  174. List::new(nodes).block(Block::default().borders(Borders::ALL)).highlight_symbol(">> ");
  175. f.render_stateful_widget(nodes, slice[0], &mut self.id_menu.state);
  176. Ok(())
  177. }
  178. fn parse_msg_list(&self, connect_id: String) -> DnetViewResult<List<'a>> {
  179. let send_style = Style::default().fg(Color::LightCyan);
  180. let recv_style = Style::default().fg(Color::DarkGray);
  181. let mut texts = Vec::new();
  182. let mut lines = Vec::new();
  183. let log = self.msg_list.msg_map.get(&connect_id);
  184. match log {
  185. Some(values) => {
  186. for (i, (t, k, v)) in values.iter().enumerate() {
  187. lines.push(match k.as_str() {
  188. "send" => {
  189. Span::styled(format!("{} {} S: {}", i, t, v), send_style)
  190. }
  191. "recv" => {
  192. Span::styled(format!("{} {} R: {}", i, t, v), recv_style)
  193. }
  194. data => return Err(DnetViewError::UnexpectedData(data.to_string())),
  195. });
  196. }
  197. }
  198. None => return Err(DnetViewError::CannotFindId),
  199. }
  200. for line in lines.clone() {
  201. let text = ListItem::new(line);
  202. texts.push(text);
  203. }
  204. let msg_list = List::new(texts).block(Block::default().borders(Borders::ALL));
  205. Ok(msg_list)
  206. }
  207. fn render_info<B: Backend>(
  208. &mut self,
  209. f: &mut Frame<'_, B>,
  210. slice: Vec<Rect>,
  211. selected: String,
  212. ) -> DnetViewResult<()> {
  213. let style = Style::default();
  214. let mut lines = Vec::new();
  215. if self.selectables.is_empty() {
  216. // we have not received any selectable data
  217. return Ok(())
  218. } else {
  219. let info = self.selectables.get(&selected);
  220. match info {
  221. Some(SelectableObject::Node(node)) => {
  222. match &node.external_addr {
  223. Some(addr) => {
  224. let node_info = Span::styled(format!("External addr: {}", addr), style);
  225. lines.push(Spans::from(node_info));
  226. }
  227. None => {
  228. let node_info = Span::styled("External addr: Null".to_string(), style);
  229. lines.push(Spans::from(node_info));
  230. }
  231. }
  232. lines.push(Spans::from(Span::styled(
  233. format!("P2P state: {}", node.state),
  234. style,
  235. )));
  236. }
  237. Some(SelectableObject::Session(session)) => {
  238. if session.accept_addr.is_some() {
  239. let session_info = Span::styled(
  240. format!("Accept addr: {}", session.accept_addr.as_ref().unwrap()),
  241. style,
  242. );
  243. lines.push(Spans::from(session_info));
  244. }
  245. }
  246. Some(SelectableObject::Connect(connect)) => {
  247. let text = self.parse_msg_list(connect.id.clone())?;
  248. f.render_stateful_widget(text, slice[1], &mut self.msg_list.state);
  249. }
  250. None => return Err(DnetViewError::NotSelectableObject),
  251. }
  252. }
  253. let graph = Paragraph::new(lines)
  254. .block(Block::default().borders(Borders::ALL))
  255. .style(Style::default());
  256. f.render_widget(graph, slice[1]);
  257. Ok(())
  258. }
  259. }
  260. #[derive(Debug, Clone)]
  261. pub struct IdMenu {
  262. pub state: ListState,
  263. pub ids: Vec<String>,
  264. }
  265. impl IdMenu {
  266. pub fn new(ids: Vec<String>) -> IdMenu {
  267. IdMenu { state: ListState::default(), ids }
  268. }
  269. pub fn next(&mut self) {
  270. let i = match self.state.selected() {
  271. Some(i) => {
  272. if i >= self.ids.len() - 1 {
  273. 0
  274. } else {
  275. i + 1
  276. }
  277. }
  278. None => 0,
  279. };
  280. self.state.select(Some(i));
  281. }
  282. pub fn previous(&mut self) {
  283. let i = match self.state.selected() {
  284. Some(i) => {
  285. if i == 0 {
  286. self.ids.len() - 1
  287. } else {
  288. i - 1
  289. }
  290. }
  291. None => 0,
  292. };
  293. self.state.select(Some(i));
  294. }
  295. pub fn unselect(&mut self) {
  296. self.state.select(None);
  297. }
  298. }
  299. #[derive(Debug, Clone)]
  300. pub struct MsgList {
  301. pub state: ListState,
  302. pub msg_map: MsgMap,
  303. pub index: usize,
  304. }
  305. impl MsgList {
  306. pub fn new(msg_map: MsgMap, index: usize) -> MsgList {
  307. MsgList { state: ListState::default(), msg_map, index }
  308. }
  309. // TODO: reimplement
  310. //pub fn next(&mut self) {
  311. // let i = match self.state.selected() {
  312. // Some(i) => {
  313. // if i >= self.msg_len - 1 {
  314. // 0
  315. // } else {
  316. // i + 1
  317. // }
  318. // }
  319. // None => 0,
  320. // };
  321. // self.state.select(Some(i));
  322. //}
  323. //pub fn previous(&mut self) {
  324. // let i = match self.state.selected() {
  325. // Some(i) => {
  326. // if i == 0 {
  327. // self.msg_len - 1
  328. // } else {
  329. // i - 1
  330. // }
  331. // }
  332. // None => 0,
  333. // };
  334. // self.state.select(Some(i));
  335. //}
  336. pub fn scroll(&mut self) -> DnetViewResult<()> {
  337. let i = match self.state.selected() {
  338. Some(i) => i + self.index,
  339. None => 0,
  340. };
  341. self.state.select(Some(i));
  342. Ok(())
  343. }
  344. pub fn unselect(&mut self) {
  345. self.state.select(None);
  346. }
  347. }
  348. #[derive(Debug, Clone)]
  349. pub struct NodeInfoView {
  350. pub index: usize,
  351. pub infos: FxHashMap<String, NodeInfo>,
  352. }
  353. impl NodeInfoView {
  354. pub fn new(infos: FxHashMap<String, NodeInfo>) -> NodeInfoView {
  355. let index = 0;
  356. NodeInfoView { index, infos }
  357. }
  358. //pub fn next(&mut self) {
  359. // self.index = (self.index + 1) % self.infos.len();
  360. //}
  361. //pub fn previous(&mut self) {
  362. // if self.index > 0 {
  363. // self.index -= 1;
  364. // } else {
  365. // self.index = self.infos.len() - 1;
  366. // }
  367. //}
  368. }