From c80f53a3225cfec30b26532471812c3aa3e9995b Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Fri, 4 Aug 2023 13:13:19 +0200 Subject: [PATCH] dnetview: improve NodeInfo data type + re-enable view Pass inbound and outbound as two Vec into NodeInfo. Update and re-enable UI. --- bin/dnetview/src/model.rs | 12 +- bin/dnetview/src/parser.rs | 345 +++++++++++++++++++++++-------------- bin/dnetview/src/util.rs | 5 +- bin/dnetview/src/view.rs | 131 ++++++++------ 4 files changed, 300 insertions(+), 193 deletions(-) diff --git a/bin/dnetview/src/model.rs b/bin/dnetview/src/model.rs index 79c5ce125..69f4e6de2 100644 --- a/bin/dnetview/src/model.rs +++ b/bin/dnetview/src/model.rs @@ -63,7 +63,8 @@ pub struct NodeInfo { pub dnet_id: String, pub name: String, pub hosts: Vec, - pub info: Vec, + pub inbound: Vec, + pub outbound: Vec, pub is_offline: bool, } @@ -72,10 +73,11 @@ impl NodeInfo { dnet_id: String, name: String, hosts: Vec, - info: Vec, + inbound: Vec, + outbound: Vec, is_offline: bool, ) -> Self { - Self { dnet_id, name, hosts, info, is_offline } + Self { dnet_id, name, hosts, inbound, outbound, is_offline } } } @@ -109,7 +111,7 @@ pub struct SlotInfo { pub dnet_id: String, pub node_id: String, pub addr: String, - pub random_id: String, + pub random_id: u64, pub remote_id: String, pub log: Vec<(NanoTimestamp, String, String)>, pub is_empty: bool, @@ -121,7 +123,7 @@ impl SlotInfo { dnet_id: String, node_id: String, addr: String, - random_id: String, + random_id: u64, remote_id: String, log: Vec<(NanoTimestamp, String, String)>, is_empty: bool, diff --git a/bin/dnetview/src/parser.rs b/bin/dnetview/src/parser.rs index fd529f162..b20bd6e22 100644 --- a/bin/dnetview/src/parser.rs +++ b/bin/dnetview/src/parser.rs @@ -33,7 +33,7 @@ use crate::{ LilithInfo, Model, NetworkInfo, NodeInfo, SelectableObject, Session, SessionInfo, SlotInfo, }, rpc::RpcConnect, - util::{is_empty_session, make_connect_id, make_empty_id, make_node_id, make_session_id}, + util::{is_empty_session, make_empty_id, make_info_id, make_node_id, make_session_id}, }; pub struct DataParser { @@ -118,19 +118,19 @@ impl DataParser { } // If poll times out, inititalize data structures with empty values. - async fn parse_offline(&self, name: String) -> DnetViewResult<()> { + async fn parse_offline(&self, node_name: String) -> DnetViewResult<()> { let name = "Offline".to_string(); let session_type = Session::Offline; let mut slots: Vec = Vec::new(); - let mut info: Vec = Vec::new(); + let mut sessions: Vec = Vec::new(); let hosts = Vec::new(); - let node_id = make_node_id(&name)?; + let node_id = make_node_id(&node_name)?; let dnet_id = make_empty_id(&node_id, &session_type, 0)?; let addr = "Null".to_string(); let state = "Null".to_string(); - let random_id = "Null".to_string(); + let random_id = 0; let remote_id = "Null".to_string(); let log = Vec::new(); let is_empty = true; @@ -155,11 +155,19 @@ impl DataParser { slots, is_empty, ); - info.push(session_info); + sessions.push(session_info); - let node = NodeInfo::new(node_id.clone(), name.clone(), hosts, info.clone(), is_empty); + // TODO: clean this up + let node = NodeInfo::new( + node_id.clone(), + name.clone(), + hosts, + sessions.clone(), + sessions.clone(), + is_empty, + ); - self.update_selectables(info, node).await?; + self.update_selectables(node).await?; Ok(()) } @@ -178,14 +186,10 @@ impl DataParser { let inbound = self.parse_inbound(inbound, &dnet_id).await?; let outbound = self.parse_outbound(outbound, &dnet_id).await?; - let mut info: Vec = Vec::new(); - info.push(inbound.clone()); - info.push(outbound.clone()); + let node = NodeInfo::new(dnet_id, name, hosts, inbound.clone(), outbound.clone(), false); - let node = NodeInfo::new(dnet_id, name, hosts, info.clone(), false); - - self.update_selectables(info.clone(), node).await?; - self.update_msgs(info.clone()).await?; + self.update_selectables(node).await?; + self.update_msgs(inbound.clone(), outbound.clone()).await?; Ok(()) } @@ -222,24 +226,24 @@ impl DataParser { Ok(()) } - async fn update_msgs(&self, sessions: Vec) -> DnetViewResult<()> { - for session in sessions { - for connection in session.info { - if !self.model.msg_map.lock().await.contains_key(&connection.dnet_id) { + async fn update_msgs( + &self, + inbounds: Vec, + outbounds: Vec, + ) -> DnetViewResult<()> { + for inbound in inbounds { + for info in inbound.info { + if !self.model.msg_map.lock().await.contains_key(&info.dnet_id) { // we don't have this ID: it is a new node - self.model - .msg_map - .lock() - .await - .insert(connection.dnet_id, connection.log.clone()); + self.model.msg_map.lock().await.insert(info.dnet_id, info.log.clone()); } else { // we have this id: append the msg values - match self.model.msg_map.lock().await.entry(connection.dnet_id) { + match self.model.msg_map.lock().await.entry(info.dnet_id) { Entry::Vacant(e) => { - e.insert(connection.log); + e.insert(info.log); } Entry::Occupied(mut e) => { - for msg in connection.log { + for msg in info.log { e.get_mut().push(msg); } } @@ -247,35 +251,70 @@ impl DataParser { } } } + for outbound in outbounds { + for info in outbound.info { + if !self.model.msg_map.lock().await.contains_key(&info.dnet_id) { + // we don't have this ID: it is a new node + self.model.msg_map.lock().await.insert(info.dnet_id, info.log.clone()); + } else { + // we have this id: append the msg values + match self.model.msg_map.lock().await.entry(info.dnet_id) { + Entry::Vacant(e) => { + e.insert(info.log); + } + Entry::Occupied(mut e) => { + for msg in info.log { + e.get_mut().push(msg); + } + } + } + } + } + } + Ok(()) } - async fn update_selectables( - &self, - sessions: Vec, - node: NodeInfo, - ) -> DnetViewResult<()> { + async fn update_selectables(&self, node: NodeInfo) -> DnetViewResult<()> { if node.is_offline { let node_obj = SelectableObject::Node(node.clone()); self.model.selectables.lock().await.insert(node.dnet_id.clone(), node_obj.clone()); } else { let node_obj = SelectableObject::Node(node.clone()); self.model.selectables.lock().await.insert(node.dnet_id.clone(), node_obj.clone()); - for session in sessions { - if !session.is_empty { - let session_obj = SelectableObject::Session(session.clone()); + for inbound in node.inbound { + if !inbound.is_empty { + let inbound_obj = SelectableObject::Session(inbound.clone()); self.model .selectables .lock() .await - .insert(session.clone().dnet_id, session_obj.clone()); - for connect in session.info { - let connect_obj = SelectableObject::Connect(connect.clone()); + .insert(inbound.clone().dnet_id, inbound_obj.clone()); + for info in inbound.info { + let info_obj = SelectableObject::Connect(info.clone()); self.model .selectables .lock() .await - .insert(connect.clone().dnet_id, connect_obj.clone()); + .insert(info.clone().dnet_id, info_obj.clone()); + } + } + } + for outbound in node.outbound { + if !outbound.is_empty { + let outbound_obj = SelectableObject::Session(outbound.clone()); + self.model + .selectables + .lock() + .await + .insert(outbound.clone().dnet_id, outbound_obj.clone()); + for info in outbound.info { + let info_obj = SelectableObject::Connect(info.clone()); + self.model + .selectables + .lock() + .await + .insert(info.clone().dnet_id, info_obj.clone()); } } } @@ -283,35 +322,43 @@ impl DataParser { Ok(()) } - async fn _parse_external_addr(&self, addr: &Option<&Value>) -> DnetViewResult> { - match addr { - Some(addr) => match addr.as_str() { - Some(addr) => Ok(Some(addr.to_string())), - None => Ok(None), - }, - None => Err(DnetViewError::NoExternalAddr), - } - } + //async fn _parse_external_addr(&self, addr: &Option<&Value>) -> DnetViewResult> { + // match addr { + // Some(addr) => match addr.as_str() { + // Some(addr) => Ok(Some(addr.to_string())), + // None => Ok(None), + // }, + // None => Err(DnetViewError::NoExternalAddr), + // } + //} - async fn parse_inbound(&self, reply: &Value, node_id: &String) -> DnetViewResult { + async fn parse_inbound( + &self, + reply: &Value, + node_id: &String, + ) -> DnetViewResult> { let name = "Inbound".to_string(); let session_type = Session::Inbound; - let dnet_id = make_session_id(node_id, &session_type)?; - let mut info: Vec = Vec::new(); + let session_id = make_session_id(node_id, &session_type)?; + let mut slots: Vec = Vec::new(); + let mut sessions: Vec = Vec::new(); // TODO: improve this ugly hack. - let slot_count = 0; + let mut slot_count = 0; // Dnetview is not enabled. + // TODO: print warning on view if reply.is_null() { + slot_count += 1; let dnet_id = make_empty_id(node_id, &session_type, slot_count)?; let node_id = node_id.to_string(); let addr = "Null".to_string(); - let random_id = "Null".to_string(); + let random_id = 0; let remote_id = "Null".to_string(); let log = Vec::new(); let is_empty = true; + // info_id let slot = SlotInfo::new( dnet_id.clone(), node_id.clone(), @@ -321,47 +368,51 @@ impl DataParser { log, is_empty, ); - info.push(slot); + slots.push(slot); // Check whether the session is empty. - let is_empty = is_empty_session(&info); + // TODO + // let is_empty = is_empty_session(&slot); + let is_empty = true; let addr = "Null".to_string(); let state = "Null".to_string(); let session = SessionInfo::new( - dnet_id.clone(), + session_id.clone(), node_id.clone(), - name, + name.clone(), addr, state, - info, + slots.clone(), is_empty, ); - - return Ok(session) + sessions.push(session); } match reply.get("inbound") { Some(session) => { let inbound: Vec> = serde_json::from_value(session.clone()).unwrap(); - for slot in inbound { - let addr = slot.get("addr").unwrap().as_str().unwrap().to_string(); - let slot_info: serde_json::Map = - serde_json::from_value(slot.get("info").unwrap().clone()).unwrap(); + for session in inbound { + let addr = session.get("addr").unwrap().as_str().unwrap().to_string(); + let info: serde_json::Map = + serde_json::from_value(session.get("info").unwrap().clone()).unwrap(); - let slot_addr = slot_info.get("addr").unwrap().as_str().unwrap().to_string(); - let random_id = - slot_info.get("random_id").unwrap().as_str().unwrap().to_string(); - let remote_id = - slot_info.get("remote_id").unwrap().as_str().unwrap().to_string(); + let slot_addr = info.get("addr").unwrap().as_str().unwrap().to_string(); + let random_id = info.get("random_id").unwrap().as_u64().unwrap(); + let remote_id = info.get("remote_id").unwrap().as_str().unwrap().to_string(); + let info_id = make_info_id(&random_id)?; let log: Vec<(NanoTimestamp, String, String)> = - serde_json::from_value(slot_info.get("log").unwrap().clone()).unwrap(); + serde_json::from_value(info.get("log").unwrap().clone()).unwrap(); + + // ... let node_id = node_id.to_string(); let is_empty = false; + // TODO: make this an option + let state = String::new(); let slot = SlotInfo::new( - dnet_id.clone(), + info_id.clone(), node_id.clone(), slot_addr, random_id, @@ -369,80 +420,88 @@ impl DataParser { log, is_empty, ); - info.push(slot); - } + slots.push(slot); - // TODO: fixme - let addr = String::new(); - // TODO: this should be an option - let state = String::new(); - let node_id = node_id.to_string(); - let is_empty = false; - let session = - SessionInfo::new(dnet_id, node_id.clone(), name, addr, state, info, is_empty); - Ok(session) + let session = SessionInfo::new( + session_id.clone(), + node_id.clone(), + name.clone(), + addr, + state, + slots.clone(), + is_empty, + ); + sessions.push(session); + } } None => { // Empty data. Initialize empty values. // TODO: clean up empty info boilerplate. - let dnet_id = make_empty_id(node_id, &session_type, slot_count)?; + slot_count += 1; + let info_id = make_empty_id(node_id, &session_type, slot_count)?; let node_id = node_id.to_string(); let addr = "Null".to_string(); - let random_id = "Null".to_string(); + let random_id = 0; let remote_id = "Null".to_string(); let log = Vec::new(); let is_empty = true; let slot = SlotInfo::new( - dnet_id.clone(), + info_id.clone(), node_id.clone(), - addr, + addr.clone(), random_id, remote_id, log, is_empty, ); - info.push(slot); + slots.push(slot); // Check whether the session is empty. - let is_empty = is_empty_session(&info); + let is_empty = is_empty_session(&slots); - let addr = "Null".to_string(); let state = "Null".to_string(); let session = SessionInfo::new( - dnet_id.clone(), + session_id.clone(), node_id.clone(), name, addr, state, - info, + slots, is_empty, ); - return Ok(session) + sessions.push(session); } } + Ok(sessions) } - async fn parse_outbound(&self, reply: &Value, node_id: &String) -> DnetViewResult { + async fn parse_outbound( + &self, + reply: &Value, + node_id: &String, + ) -> DnetViewResult> { let name = "Outbound".to_string(); let session_type = Session::Outbound; - let dnet_id = make_session_id(node_id, &session_type)?; - let mut info: Vec = Vec::new(); + let session_id = make_session_id(node_id, &session_type)?; + let mut slots: Vec = Vec::new(); + let mut sessions: Vec = Vec::new(); // TODO: improve this ugly hack. let mut slot_count = 0; // Dnetview is not enabled. if reply.is_null() { - let dnet_id = make_empty_id(&node_id, &session_type, slot_count)?; + slot_count += 1; + let info_id = make_empty_id(&node_id, &session_type, slot_count)?; let node_id = node_id.to_string(); let addr = "Null".to_string(); - let random_id = "Null".to_string(); + let random_id = 0; let remote_id = "Null".to_string(); let log = Vec::new(); let is_empty = false; let slot = SlotInfo::new( - dnet_id.clone(), + info_id.clone(), node_id.clone(), addr, random_id, @@ -450,32 +509,48 @@ impl DataParser { log, is_empty, ); - info.push(slot); + slots.push(slot); + // Check whether the session is empty. + let is_empty = is_empty_session(&slots); + + let addr = "Null".to_string(); + let state = "Null".to_string(); + let session = SessionInfo::new( + session_id.clone(), + node_id.clone(), + name.clone(), + addr, + state, + slots.clone(), + is_empty, + ); + sessions.push(session); } match reply.get("outbound") { Some(session) => { - let inbound: Vec> = + let outbound: Vec> = serde_json::from_value(session.clone()).unwrap(); - for slot in inbound { - let addr = slot.get("addr").unwrap().as_str().unwrap().to_string(); - let state = slot.get("state").unwrap().as_str().unwrap().to_string(); - let slot_info: serde_json::Map = - serde_json::from_value(slot.get("info").unwrap().clone()).unwrap(); + for session in outbound { + let addr = session.get("addr").unwrap().as_str().unwrap().to_string(); + let state = session.get("state").unwrap().as_str().unwrap().to_string(); + let info: serde_json::Map = + serde_json::from_value(session.get("info").unwrap().clone()).unwrap(); - let slot_addr = slot_info.get("addr").unwrap().as_str().unwrap().to_string(); - let random_id = - slot_info.get("random_id").unwrap().as_str().unwrap().to_string(); - let remote_id = - slot_info.get("remote_id").unwrap().as_str().unwrap().to_string(); + let slot_addr = info.get("addr").unwrap().as_str().unwrap().to_string(); + let random_id = info.get("random_id").unwrap().as_u64().unwrap(); + let remote_id = info.get("remote_id").unwrap().as_str().unwrap().to_string(); + let info_id = make_info_id(&random_id)?; let log: Vec<(NanoTimestamp, String, String)> = - serde_json::from_value(slot_info.get("log").unwrap().clone()).unwrap(); + serde_json::from_value(info.get("log").unwrap().clone()).unwrap(); + + // ... let node_id = node_id.to_string(); let is_empty = false; let slot = SlotInfo::new( - dnet_id.clone(), + info_id.clone(), node_id.clone(), slot_addr, random_id, @@ -483,57 +558,59 @@ impl DataParser { log, is_empty, ); - info.push(slot); - } + slots.push(slot); - // TODO: fixme - let addr = String::new(); - // TODO: this should be an option - let state = String::new(); - let node_id = node_id.to_string(); - let is_empty = false; - let session = - SessionInfo::new(dnet_id, node_id.clone(), name, addr, state, info, is_empty); - Ok(session) + let session = SessionInfo::new( + session_id.clone(), + node_id.clone(), + name.clone(), + addr.clone(), + state, + slots.clone(), + is_empty, + ); + sessions.push(session); + } } None => { // Empty data. Initialize empty values. // TODO: clean up empty info boilerplate. - let dnet_id = make_empty_id(node_id, &session_type, slot_count)?; + slot_count += 1; + let info_id = make_empty_id(node_id, &session_type, slot_count)?; let node_id = node_id.to_string(); let addr = "Null".to_string(); - let random_id = "Null".to_string(); + let random_id = 0; let remote_id = "Null".to_string(); let log = Vec::new(); let is_empty = true; let slot = SlotInfo::new( - dnet_id.clone(), + info_id.clone(), node_id.clone(), - addr, + addr.clone(), random_id, remote_id, log, is_empty, ); - info.push(slot); + slots.push(slot); // Check whether the session is empty. - let is_empty = is_empty_session(&info); + let is_empty = is_empty_session(&slots); - let addr = "Null".to_string(); let state = "Null".to_string(); let session = SessionInfo::new( - dnet_id.clone(), + session_id.clone(), node_id.clone(), name, - addr, + addr.clone(), state, - info, + slots, is_empty, ); - return Ok(session) + sessions.push(session); } } + Ok(sessions) } async fn parse_hosts(&self, hosts: &Value) -> DnetViewResult> { diff --git a/bin/dnetview/src/util.rs b/bin/dnetview/src/util.rs index ee85d264d..fb414eed7 100644 --- a/bin/dnetview/src/util.rs +++ b/bin/dnetview/src/util.rs @@ -49,9 +49,9 @@ pub fn make_session_id(node_id: &str, session: &Session) -> Result { Ok(id) } -pub fn make_connect_id(id: &u64) -> Result { +pub fn make_info_id(id: &u64) -> Result { let mut id = hex::encode(id.to_ne_bytes()); - id.insert_str(0, "CONNECT"); + id.insert_str(0, "INFO"); Ok(id) } @@ -118,6 +118,7 @@ pub fn make_empty_id(node_id: &str, session: &Session, count: u64) -> Result bool { return connects.iter().all(|conn| conn.is_empty) } diff --git a/bin/dnetview/src/view.rs b/bin/dnetview/src/view.rs index 569ee7e77..45b1b83e8 100644 --- a/bin/dnetview/src/view.rs +++ b/bin/dnetview/src/view.rs @@ -17,6 +17,7 @@ */ use std::collections::HashMap; +//use log::debug; use tui::{ backend::Backend, @@ -92,14 +93,26 @@ impl<'a> View { self.ordered_list.push(node.dnet_id.clone()); } if !node.is_offline { - for session in &node.info { - if !session.is_empty { - if !self.ordered_list.iter().any(|i| i == &session.dnet_id) { - self.ordered_list.push(session.dnet_id.clone()); + 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()); } - for connection in &session.info { - if !self.ordered_list.iter().any(|i| i == &connection.dnet_id) { - self.ordered_list.push(connection.dnet_id.clone()); + for info in &inbound.info { + if !self.ordered_list.iter().any(|i| i == &info.dnet_id) { + self.ordered_list.push(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()); + } + for info in &outbound.info { + if !self.ordered_list.iter().any(|i| i == &info.dnet_id) { + self.ordered_list.push(info.dnet_id.clone()); } } } @@ -201,38 +214,71 @@ impl<'a> View { let lines = vec![Spans::from(name_span)]; let names = ListItem::new(lines); nodes.push(names); - for session in &node.info { - if !session.is_empty { - let name = Span::styled(format!(" {}", session.name), style); + + for inbound in &node.inbound { + if !inbound.is_empty { + let name = Span::styled(format!(" {}", inbound.name), style); let lines = vec![Spans::from(name)]; let names = ListItem::new(lines); nodes.push(names); - for connection in &session.info { - let mut info = Vec::new(); - match connection.addr.as_str() { + for info in &inbound.info { + let mut infos = Vec::new(); + match info.addr.as_str() { "Null" => { let style = Style::default() .fg(Color::Blue) .add_modifier(Modifier::ITALIC); let name = Span::styled( - format!(" {} ", connection.addr), + format!(" {} ", info.addr), style, ); - info.push(name); + infos.push(name); } addr => { let name = Span::styled( - format!( - " {} ({})", - addr, connection.remote_id - ), + format!(" {} ({})", addr, info.remote_id), style, ); - info.push(name); + infos.push(name); } } - let lines = vec![Spans::from(info)]; + let lines = vec![Spans::from(infos)]; + let names = ListItem::new(lines); + nodes.push(names); + } + } + } + + for outbound in &node.outbound { + if !outbound.is_empty { + let name = Span::styled(format!(" {}", outbound.name), style); + let lines = vec![Spans::from(name)]; + let names = ListItem::new(lines); + nodes.push(names); + for info in &outbound.info { + let mut infos = Vec::new(); + match info.addr.as_str() { + "Null" => { + let style = Style::default() + .fg(Color::Blue) + .add_modifier(Modifier::ITALIC); + let name = Span::styled( + format!(" {} ", info.addr), + style, + ); + infos.push(name); + } + addr => { + let name = Span::styled( + format!(" {} ({})", addr, info.remote_id), + style, + ); + infos.push(name); + } + } + + let lines = vec![Spans::from(infos)]; let names = ListItem::new(lines); nodes.push(names); } @@ -263,12 +309,12 @@ impl<'a> View { Ok(()) } - fn parse_msg_list(&self, connect_id: String) -> DnetViewResult> { + fn parse_msg_list(&self, info_id: String) -> DnetViewResult> { 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(&connect_id); + let log = self.msg_list.msg_map.get(&info_id); match log { Some(values) => { for (i, (t, k, v)) in values.iter().enumerate() { @@ -315,38 +361,19 @@ impl<'a> View { Some(SelectableObject::Node(node)) => { //debug!(target: "dnetview", "render_info()::SelectableObject::Node"); lines.push(Spans::from(Span::styled("Type: Normal", style))); - //match &node.external_addr { - // Some(addr) => { - // let node_info = Span::styled(format!("External addr: {}", addr), style); - // lines.push(Spans::from(node_info)); - // } - // None => { - // let node_info = Span::styled("External addr: Null".to_string(), style); - // lines.push(Spans::from(node_info)); - // } - //} - //lines.push(Spans::from(Span::styled( - // format!("P2P state: {}", node.state), - // style, - //))); + lines.push(Spans::from(Span::styled("Hosts:", style))); + for host in &node.hosts { + lines.push(Spans::from(Span::styled(format!(" {}", host), style))); + } } Some(SelectableObject::Session(session)) => { //debug!(target: "dnetview", "render_info()::SelectableObject::Session"); - //if session.addr.is_some() { - // let accept_addr = Span::styled( - // format!("Accept addr: {}", session.addr.as_ref().unwrap()), - // style, - // ); - // lines.push(Spans::from(accept_addr)); - //} - //if session.hosts.is_some() { - // let hosts = Span::styled("Hosts:".to_string(), style); - // lines.push(Spans::from(hosts)); - // for host in session.hosts.as_ref().unwrap() { - // let host = Span::styled(format!(" {}", host), style); - // lines.push(Spans::from(host)); - // } - //} + let addr = Span::styled(format!("Addr: {}", session.addr), style); + lines.push(Spans::from(addr)); + + // TODO: this will be an option + let addr = Span::styled(format!("State: {}", session.state), style); + lines.push(Spans::from(addr)); } Some(SelectableObject::Connect(connect)) => { //debug!(target: "dnetview", "render_info()::SelectableObject::Connect");