dnetview: improve NodeInfo data type + re-enable view

Pass inbound and outbound as two Vec<SessionInfo> into NodeInfo.
Update and re-enable UI.
This commit is contained in:
lunar-mining
2023-08-04 13:13:19 +02:00
parent 12e74f0a22
commit c80f53a322
4 changed files with 300 additions and 193 deletions

View File

@@ -63,7 +63,8 @@ pub struct NodeInfo {
pub dnet_id: String,
pub name: String,
pub hosts: Vec<String>,
pub info: Vec<SessionInfo>,
pub inbound: Vec<SessionInfo>,
pub outbound: Vec<SessionInfo>,
pub is_offline: bool,
}
@@ -72,10 +73,11 @@ impl NodeInfo {
dnet_id: String,
name: String,
hosts: Vec<String>,
info: Vec<SessionInfo>,
inbound: Vec<SessionInfo>,
outbound: Vec<SessionInfo>,
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,

View File

@@ -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<SlotInfo> = Vec::new();
let mut info: Vec<SessionInfo> = Vec::new();
let mut sessions: Vec<SessionInfo> = 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<SessionInfo> = 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<SessionInfo>) -> 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<SessionInfo>,
outbounds: Vec<SessionInfo>,
) -> 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<SessionInfo>,
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<Option<String>> {
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<Option<String>> {
// 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<SessionInfo> {
async fn parse_inbound(
&self,
reply: &Value,
node_id: &String,
) -> DnetViewResult<Vec<SessionInfo>> {
let name = "Inbound".to_string();
let session_type = Session::Inbound;
let dnet_id = make_session_id(node_id, &session_type)?;
let mut info: Vec<SlotInfo> = Vec::new();
let session_id = make_session_id(node_id, &session_type)?;
let mut slots: Vec<SlotInfo> = Vec::new();
let mut sessions: Vec<SessionInfo> = 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::Map<String, Value>> =
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<String, Value> =
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<String, Value> =
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<SessionInfo> {
async fn parse_outbound(
&self,
reply: &Value,
node_id: &String,
) -> DnetViewResult<Vec<SessionInfo>> {
let name = "Outbound".to_string();
let session_type = Session::Outbound;
let dnet_id = make_session_id(node_id, &session_type)?;
let mut info: Vec<SlotInfo> = Vec::new();
let session_id = make_session_id(node_id, &session_type)?;
let mut slots: Vec<SlotInfo> = Vec::new();
let mut sessions: Vec<SessionInfo> = 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<serde_json::Map<String, Value>> =
let outbound: Vec<serde_json::Map<String, Value>> =
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<String, Value> =
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<String, Value> =
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<Vec<String>> {

View File

@@ -49,9 +49,9 @@ pub fn make_session_id(node_id: &str, session: &Session) -> Result<String> {
Ok(id)
}
pub fn make_connect_id(id: &u64) -> Result<String> {
pub fn make_info_id(id: &u64) -> Result<String> {
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<Str
Ok(id)
}
// TODO: Rename to is empty slot.
pub fn is_empty_session(connects: &[SlotInfo]) -> bool {
return connects.iter().all(|conn| conn.is_empty)
}

View File

@@ -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<List<'a>> {
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(&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");