Przeglądaj źródła

dnetview/ View: refactor

* create view objects to handle model data
* update() function to write model to view
* render() to draw view

this commit deprecates ui::ui()
lunar-mining 4 lat temu
rodzic
commit
8b5f56f54e

+ 1 - 1
bin/dnetview/src/lib.rs

@@ -8,5 +8,5 @@ pub mod view;
 pub use config::{DnvConfig, CONFIG_FILE_CONTENTS};
 //pub use model::Model;
 pub use options::ProgramOptions;
-pub use ui::ui;
+//pub use ui::ui;
 //pub use view::{IdListView, InfoListView, View};

+ 47 - 17
bin/dnetview/src/main.rs

@@ -31,7 +31,7 @@ use dnetview::{
     options::ProgramOptions,
     ui,
     util::{generate_id, make_connect_id, make_node_id, make_session_id},
-    view::{IdListView, InfoListView, View},
+    view::{ConnectInfoView, IdListView, InfoListView, NodeInfoView, SessionInfoView, View},
 };
 
 struct DNetView {
@@ -108,7 +108,7 @@ async fn main() -> Result<()> {
     terminal.clear()?;
 
     let ids = Mutex::new(FxHashSet::default());
-    let infos = Mutex::new(FxHashMap::default());
+    let infos = Mutex::new(Vec::new());
 
     let model = Arc::new(Model::new(ids, infos));
 
@@ -180,10 +180,11 @@ async fn parse_data(
     let node_info = NodeInfo::new(node_id.clone(), node_name.to_string(), sessions.clone());
     let node = SelectableObject::Node(node_info.clone());
 
+    // we might simplify things by making this a Vec<SelectableObject>
     update_model(model.clone(), sessions.clone(), node_info, node).await?;
 
-    debug!("IDS: {:?}", model.ids.lock().await);
-    debug!("INFOS: {:?}", model.infos.lock().await);
+    //debug!("IDS: {:?}", model.ids.lock().await);
+    //debug!("INFOS: {:?}", model.infos.lock().await);
 
     Ok(())
 }
@@ -194,19 +195,27 @@ async fn update_model(
     node_info: NodeInfo,
     node: SelectableObject,
 ) -> Result<()> {
+    model.ids.lock().await.insert(node_info.node_id.clone());
+    model.infos.lock().await.push(node);
+
     for session in sessions.clone() {
-        model.ids.lock().await.insert(session.session_id);
+        model.ids.lock().await.insert(session.clone().session_id);
+
+        let session_obj = SelectableObject::Session(session.clone());
+        model.infos.lock().await.push(session_obj);
+
         for connect in session.children {
-            model.ids.lock().await.insert(connect.connect_id);
+            model.ids.lock().await.insert(connect.clone().connect_id);
+            let connect_obj = SelectableObject::Connect(connect.clone());
+            model.infos.lock().await.push(connect_obj);
         }
     }
-    model.ids.lock().await.insert(node_info.node_id.clone());
-    model.infos.lock().await.insert(node_info.node_id.clone(), node);
 
     Ok(())
 }
 
 async fn parse_inbound(inbound: &Value, node_id: String) -> Result<SessionInfo> {
+    let session_name = "Inbound".to_string();
     let session_type = Session::Inbound;
     let session_id = make_session_id(node_id.clone(), &session_type)?;
     let mut connects: Vec<ConnectInfo> = Vec::new();
@@ -254,8 +263,12 @@ async fn parse_inbound(inbound: &Value, node_id: String) -> Result<SessionInfo>
                     }
                 }
             }
-            let session_info =
-                SessionInfo::new(session_id.clone(), node_id.clone(), connects.clone());
+            let session_info = SessionInfo::new(
+                session_name,
+                session_id.clone(),
+                node_id.clone(),
+                connects.clone(),
+            );
             Ok(session_info)
         }
         None => Err(Error::ValueIsNotObject),
@@ -264,6 +277,7 @@ async fn parse_inbound(inbound: &Value, node_id: String) -> Result<SessionInfo>
 
 // TODO: placeholder for now
 async fn parse_manual(_manual: &Value, node_id: String) -> Result<SessionInfo> {
+    let session_name = "Manual".to_string();
     let session_type = Session::Manual;
     let mut connects: Vec<ConnectInfo> = Vec::new();
 
@@ -280,12 +294,13 @@ async fn parse_manual(_manual: &Value, node_id: String) -> Result<SessionInfo> {
     let connect_info =
         ConnectInfo::new(connect_id, addr, is_empty, msg, status, state, msg_log, parent);
     connects.push(connect_info.clone());
-    let session_info = SessionInfo::new(session_id, node_id, connects.clone());
+    let session_info = SessionInfo::new(session_name, session_id, node_id, connects.clone());
 
     Ok(session_info)
 }
 
 async fn parse_outbound(outbound: &Value, node_id: String) -> Result<SessionInfo> {
+    let session_name = "Outbound".to_string();
     let session_type = Session::Outbound;
     let mut connects: Vec<ConnectInfo> = Vec::new();
     let slots = &outbound["slots"];
@@ -347,7 +362,8 @@ async fn parse_outbound(outbound: &Value, node_id: String) -> Result<SessionInfo
                     }
                 }
             }
-            let session_info = SessionInfo::new(session_id, node_id, connects.clone());
+            let session_info =
+                SessionInfo::new(session_name, session_id, node_id, connects.clone());
             Ok(session_info)
         }
         None => Err(Error::ValueIsNotObject),
@@ -361,15 +377,29 @@ async fn render<B: Backend>(terminal: &mut Terminal<B>, model: Arc<Model>) -> Re
 
     let id_list = IdListView::new(FxHashSet::default());
     let info_list = InfoListView::new(FxHashMap::default());
-    let mut view = View::new(id_list.clone(), info_list.clone());
 
+    let node_view = NodeInfoView::default();
+    let session_view = SessionInfoView::default();
+    let connect_view = ConnectInfoView::default();
+
+    let mut view = View::new(
+        id_list.clone(),
+        info_list,
+        node_view.clone(),
+        session_view.clone(),
+        connect_view.clone(),
+    );
     view.id_list.state.select(Some(0));
     view.info_list.index = 0;
 
     loop {
-        view.update(model.infos.lock().await.clone());
+        //debug!("MODEL: {:?}", model.infos.lock().await.clone());
+        //debug!("VIEW BEFORE UPDATE: {:?}", &view.id_list.ids);
+        let mut view = view.clone().update(model.infos.lock().await.clone())?;
+        //debug!("VIEW AFTER UPDATE: {:?}", view.clone().id_list.ids);
+
         terminal.draw(|f| {
-            ui::ui(f, view.clone());
+            view.clone().render(f);
         })?;
         for k in asi.by_ref().keys() {
             match k.unwrap() {
@@ -378,10 +408,10 @@ async fn render<B: Backend>(terminal: &mut Terminal<B>, model: Arc<Model>) -> Re
                     return Ok(())
                 }
                 Key::Char('j') => {
-                    view.id_list.next();
+                    view.clone().id_list.next();
                 }
                 Key::Char('k') => {
-                    view.id_list.previous();
+                    view.clone().id_list.previous();
                 }
                 _ => (),
             }

+ 11 - 7
bin/dnetview/src/model.rs

@@ -19,14 +19,12 @@ pub enum SelectableObject {
 
 pub struct Model {
     pub ids: Mutex<FxHashSet<String>>,
-    pub infos: Mutex<FxHashMap<String, SelectableObject>>,
+    pub infos: Mutex<Vec<SelectableObject>>,
+    //pub infos: Mutex<FxHashMap<String, SelectableObject>>,
 }
 
 impl Model {
-    pub fn new(
-        ids: Mutex<FxHashSet<String>>,
-        infos: Mutex<FxHashMap<String, SelectableObject>>,
-    ) -> Model {
+    pub fn new(ids: Mutex<FxHashSet<String>>, infos: Mutex<Vec<SelectableObject>>) -> Model {
         Model { ids, infos }
     }
 }
@@ -46,14 +44,20 @@ impl NodeInfo {
 
 #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash)]
 pub struct SessionInfo {
+    pub session_name: String,
     pub session_id: String,
     pub parent: String,
     pub children: Vec<ConnectInfo>,
 }
 
 impl SessionInfo {
-    pub fn new(session_id: String, parent: String, children: Vec<ConnectInfo>) -> SessionInfo {
-        SessionInfo { session_id, parent, children }
+    pub fn new(
+        session_name: String,
+        session_id: String,
+        parent: String,
+        children: Vec<ConnectInfo>,
+    ) -> SessionInfo {
+        SessionInfo { session_name, session_id, parent, children }
     }
 }
 

+ 141 - 99
bin/dnetview/src/ui.rs

@@ -1,99 +1,141 @@
-use crate::view::View;
-use log::debug;
-
-use tui::{
-    backend::Backend,
-    layout::{Constraint, Direction, Layout, Rect},
-    style::Style,
-    text::{Span, Spans},
-    widgets::{Block, Borders, List, ListItem, Paragraph},
-    Frame,
-};
-
-pub fn ui<B: Backend>(f: &mut Frame<'_, B>, mut view: View) {
-    let list_margin = 2;
-    let list_direction = Direction::Horizontal;
-    let list_cnstrnts = vec![Constraint::Percentage(50), Constraint::Percentage(50)];
-
-    let mut nodes = Vec::new();
-    let style = Style::default();
-
-    for id in &view.id_list.ids {
-        let id_span = Span::raw(id.to_string());
-        let mut lines = vec![Spans::from(id_span)];
-
-        match &view.info_list.infos.get(id) {
-            Some(node) => {
-                //debug!("NODE: {:?}", node);
-                //if !node.outbound.iter().all(|node| node.is_empty) {
-                //    lines.push(Spans::from(Span::styled("   Outgoing", Style::default())));
-                //}
-                //for outbound in &node.outbound.clone() {
-                //    for slot in outbound.slots.clone() {
-                //        let addr = Span::styled(format!("       {}", slot.addr), style);
-                //        let msg: Span = match slot.channel.last_status.as_str() {
-                //            "recv" => Span::styled(
-                //                format!("               [R: {}]", slot.channel.last_msg),
-                //                style,
-                //            ),
-                //            "sent" => Span::styled(
-                //                format!("               [S: {}]", slot.channel.last_msg),
-                //                style,
-                //            ),
-                //            a => Span::styled(a.to_string(), style),
-                //        };
-                //        lines.push(Spans::from(vec![addr, msg]));
-                //    }
-                //}
-                //if !node.inbound.iter().all(|node| node.is_empty) {
-                //    lines.push(Spans::from(Span::styled("   Incoming", Style::default())));
-                //}
-                //for inbound in &node.inbound {
-                //    let addr = Span::styled(format!("       {}", inbound.connected), style);
-                //    let msg: Span = match inbound.channel.last_status.as_str() {
-                //        "recv" => Span::styled(
-                //            format!("               [R: {}]", inbound.channel.last_msg),
-                //            style,
-                //        ),
-                //        "sent" => Span::styled(
-                //            format!("               [R: {}]", inbound.channel.last_msg),
-                //            style,
-                //        ),
-                //        a => Span::styled(a.to_string(), style),
-                //    };
-                //    lines.push(Spans::from(vec![addr, msg]));
-                //}
-                //lines.push(Spans::from(Span::styled("   Manual", Style::default())));
-                //for connect in &node.manual {
-                //    lines.push(Spans::from(Span::styled(format!("       {}", connect.key), style)));
-                //}
-            }
-            None => {
-                // TODO
-                debug!("This is also a bug");
-            }
-        }
-
-        let ids = ListItem::new(lines);
-        nodes.push(ids);
-    }
-
-    let nodes =
-        List::new(nodes).block(Block::default().borders(Borders::ALL)).highlight_symbol(">> ");
-    let slice = Layout::default()
-        .direction(list_direction)
-        .margin(list_margin)
-        .constraints(list_cnstrnts)
-        .split(f.size());
-
-    f.render_stateful_widget(nodes, slice[0], &mut view.id_list.state);
-
-    render_info_right(view.clone(), f, slice);
-}
-
-fn render_info_right<B: Backend>(_view: View, f: &mut Frame<'_, B>, slice: Vec<Rect>) {
-    let span = vec![];
-    let graph =
-        Paragraph::new(span).block(Block::default().borders(Borders::ALL)).style(Style::default());
-    f.render_widget(graph, slice[1]);
-}
+//use crate::model::SelectableObject;
+//use crate::view::View;
+//use log::debug;
+//
+//use tui::{
+//    backend::Backend,
+//    layout::{Constraint, Direction, Layout, Rect},
+//    style::Style,
+//    text::{Span, Spans},
+//    widgets::{Block, Borders, List, ListItem, Paragraph},
+//    Frame,
+//};
+//
+//pub fn ui<B: Backend>(f: &mut Frame<'_, B>, mut view: View) {
+//    let list_margin = 2;
+//    let list_direction = Direction::Horizontal;
+//    let list_cnstrnts = vec![Constraint::Percentage(50), Constraint::Percentage(50)];
+//
+//    let mut nodes = Vec::new();
+//    let style = Style::default();
+//
+//    // TODO: this is insanely nested. pass SelectableObjects to different views
+//    for id in &view.id_list.ids {
+//        let mut lines: Vec<Spans> = Vec::new();
+//        //debug!("{}", id);
+//        // this only needs to be node ids
+//        match &view.info_list.infos.get(id) {
+//            Some(node) => {
+//                match node {
+//                    SelectableObject::Node(node_info) => {
+//                        let name_span = Span::raw(node_info.node_name.to_string());
+//                        lines.push(Spans::from(name_span));
+//                        //let mut lines = vec![Spans::from(name_span)];
+//                        //let id = &node_info.node_id;
+//                        //let name = &node_info.node_name;
+//                        for child in &node_info.children {
+//                            match child.session_name.as_str() {
+//                                "Outgoing" => {
+//                                    lines.push(Spans::from(Span::styled(
+//                                        "   Outgoing",
+//                                        Style::default(),
+//                                    )));
+//                                }
+//                                "Incoming" => {
+//                                    lines.push(Spans::from(Span::styled(
+//                                        "   Outgoing",
+//                                        Style::default(),
+//                                    )));
+//                                }
+//                                "Manual" => {
+//                                    lines.push(Spans::from(Span::styled(
+//                                        "   Outgoing",
+//                                        Style::default(),
+//                                    )));
+//                                }
+//                                _ => {}
+//                            }
+//                            for child in &child.children {
+//                                // do something
+//                            }
+//                        }
+//                        //if !node.outbound.iter().all(|node| node.is_empty) {
+//                        //    lines.push(Spans::from(Span::styled("   Outgoing", Style::default())));
+//                        //}
+//
+//                        // ok
+//                    }
+//                    _ => {
+//                        // ok
+//                    }
+//                }
+//                //for outbound in &node.outbound.clone() {      LOG_TARGETS=net cargo run -- -vv --slots 5 --seed 0.0.0.0:9999 --irc 127.0.0.1:6668 --rpc 127.0.0.1:8000
+//                //    for slot in outbound.slots.clone() {
+//                //        let addr = Span::styled(format!("       {}", slot.addr), style);
+//                //        let msg: Span = match slot.channel.last_status.as_str() {
+//                //            "recv" => Span::styled(
+//                //                format!("               [R: {}]", slot.channel.last_msg),
+//                //                style,
+//                //            ),
+//                //            "sent" => Span::styled(
+//                //                format!("               [S: {}]", slot.channel.last_msg),
+//                //                style,
+//                //            ),
+//                //            a => Span::styled(a.to_string(), style),
+//                //        };
+//                //        lines.push(Spans::from(vec![addr, msg]));
+//                //    }
+//                //}
+//                //if !node.inbound.iter().all(|node| node.is_empty) {
+//                //    lines.push(Spans::from(Span::styled("   Incoming", Style::default())));
+//                //}
+//                //for inbound in &node.inbound {
+//                //    let addr = Span::styled(format!("       {}", inbound.connected), style);
+//                //    let msg: Span = match inbound.channel.last_status.as_str() {
+//                //        "recv" => Span::styled(
+//                //            format!("               [R: {}]", inbound.channel.last_msg),
+//                //            style,
+//                //        ),
+//                //        "sent" => Span::styled(
+//                //            format!("               [R: {}]", inbound.channel.last_msg),
+//                //            style,
+//                //        ),
+//                //        a => Span::styled(a.to_string(), style),
+//                //    };
+//                //    lines.push(Spans::from(vec![addr, msg]));
+//                //}
+//                //lines.push(Spans::from(Span::styled("   Manual", Style::default())));
+//                //for connect in &node.manual {
+//                //    lines.push(Spans::from(Span::styled(format!("       {}", connect.key), style)));
+//                //}
+//            }
+//            None => {
+//                // TODO
+//                debug!("This is also a bug");
+//            }
+//        }
+//
+//        // need list of all ids here
+//        let ids = ListItem::new(lines);
+//        nodes.push(ids);
+//    }
+//
+//    let nodes =
+//        List::new(nodes).block(Block::default().borders(Borders::ALL)).highlight_symbol(">> ");
+//    let slice = Layout::default()
+//        .direction(list_direction)
+//        .margin(list_margin)
+//        .constraints(list_cnstrnts)
+//        .split(f.size());
+//
+//    f.render_stateful_widget(nodes, slice[0], &mut view.id_list.state);
+//
+//    render_info_right(view.clone(), f, slice);
+//}
+//
+//fn render_info_right<B: Backend>(_view: View, f: &mut Frame<'_, B>, slice: Vec<Rect>) {
+//    let span = vec![];
+//    let graph =
+//        Paragraph::new(span).block(Block::default().borders(Borders::ALL)).style(Style::default());
+//    f.render_widget(graph, slice[1]);
+//}

+ 234 - 11
bin/dnetview/src/view.rs

@@ -1,29 +1,252 @@
+use async_std::sync::Arc;
+use darkfi::error::Result;
 use fxhash::{FxHashMap, FxHashSet};
-//use log::debug;
+use log::debug;
+use serde::{Deserialize, Serialize};
 use tui::widgets::ListState;
 
-use crate::model::SelectableObject;
+use tui::{
+    backend::Backend,
+    layout::{Constraint, Direction, Layout, Rect},
+    style::Style,
+    text::{Span, Spans},
+    widgets::{Block, Borders, List, ListItem, Paragraph},
+    Frame,
+};
 
-#[derive(Clone)]
+use crate::model::{ConnectInfo, Model, NodeInfo, SelectableObject, SessionInfo};
+
+#[derive(Debug, Clone)]
 pub struct View {
     pub id_list: IdListView,
     pub info_list: InfoListView,
+    pub node_info: NodeInfoView,
+    pub session_info: SessionInfoView,
+    pub connect_info: ConnectInfoView,
 }
 
 impl View {
-    pub fn new(id_list: IdListView, info_list: InfoListView) -> View {
-        View { id_list, info_list }
+    pub fn new(
+        id_list: IdListView,
+        info_list: InfoListView,
+        node_info: NodeInfoView,
+        session_info: SessionInfoView,
+        connect_info: ConnectInfoView,
+    ) -> View {
+        View { id_list, info_list, node_info, session_info, connect_info }
     }
 
-    pub fn update(&mut self, infos: FxHashMap<String, SelectableObject>) {
-        for (id, info) in infos {
-            self.id_list.ids.insert(id.clone());
-            self.info_list.infos.insert(id, info);
+    pub fn update(mut self, model: Vec<SelectableObject>) -> Result<View> {
+        for obj in model {
+            let obj_clone = obj.clone();
+            match obj {
+                SelectableObject::Node(node) => {
+                    let node1 = node.clone();
+                    //self.node_info.clone().update(node1.clone())?;
+                    self.id_list.ids.insert(node1.clone().node_id);
+                    self.info_list.infos.insert(node.node_id, obj_clone);
+                }
+                SelectableObject::Session(session) => {
+                    let session1 = session.clone();
+                    //self.session_info.clone().update(session1.clone())?;
+                    self.id_list.ids.insert(session1.clone().session_id);
+                    self.info_list.infos.insert(session1.clone().session_id, obj_clone);
+                }
+                SelectableObject::Connect(connect) => {
+                    let connect1 = connect.clone();
+                    //self.connect_info.clone().update(connect)?;
+                    self.id_list.ids.insert(connect1.clone().connect_id);
+                    self.info_list.infos.insert(connect1.clone().connect_id, obj_clone);
+                }
+            }
         }
+
+        let id_list = self.id_list;
+        let info_list = self.info_list;
+        let node_info = self.node_info;
+        let session_info = self.session_info;
+        let connect_info = self.connect_info;
+
+        Ok(View { id_list, info_list, node_info, session_info, connect_info })
     }
+
+    pub fn render<B: Backend>(mut self, f: &mut Frame<'_, B>) {
+        //debug!("VIEW AT RENDER {:?}", self.id_list.ids);
+        //let mut nodes = Vec::new();
+        let list_margin = 2;
+        let list_direction = Direction::Horizontal;
+        let list_cnstrnts = vec![Constraint::Percentage(50), Constraint::Percentage(50)];
+
+        let mut nodes = Vec::new();
+
+        for id in self.id_list.ids {
+            match self.info_list.infos.get(&id) {
+                Some(obj) => {
+                    match obj {
+                        SelectableObject::Node(info) => {
+                            //debug!("FOUND NODE: {:?}", info);
+                            let name_span = Span::raw(&info.node_name);
+                            let lines = vec![Spans::from(name_span)];
+                            //lines.push(Spans::from(name_span));
+                            let names = ListItem::new(lines);
+                            nodes.push(names);
+                            //nodes.push(node);
+                        }
+                        SelectableObject::Session(info) => self.session_info.clone().render(info),
+                        SelectableObject::Connect(info) => self.connect_info.clone().render(info),
+                    }
+                    //
+                }
+                None => {
+                    // TODO
+                } //
+            }
+            //
+        }
+        let slice = Layout::default()
+            .direction(list_direction)
+            .margin(list_margin)
+            .constraints(list_cnstrnts)
+            .split(f.size());
+
+        let nodes =
+            List::new(nodes).block(Block::default().borders(Borders::ALL)).highlight_symbol(">> ");
+
+        f.render_stateful_widget(nodes, slice[0], &mut self.id_list.state);
+        //for node in nodes {
+        //    f.render_stateful_widget(node, slice[0], &mut self.id_list.state);
+        //}
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct NodeInfoView {
+    pub node_id: String,
+    pub node_name: String,
+    pub children: Vec<SessionInfo>,
 }
 
-#[derive(Clone)]
+impl NodeInfoView {
+    pub fn default() -> NodeInfoView {
+        let node_id = String::new();
+        let node_name = String::new();
+        let children: Vec<SessionInfo> = Vec::new();
+        NodeInfoView { node_id, node_name, children }
+    }
+
+    pub fn update(mut self, data: NodeInfo) -> Result<()> {
+        //self.state.slect(Some(0));
+        self.node_id = data.node_id;
+        self.node_name = data.node_name;
+        self.children = data.children;
+        Ok(())
+    }
+
+    pub fn render(self, node: &NodeInfo) -> List {
+        let mut nodes = Vec::new();
+        let mut lines: Vec<Spans> = Vec::new();
+        let name_span = Span::raw(&node.node_name);
+        lines.push(Spans::from(name_span));
+        let ids = ListItem::new(lines);
+        nodes.push(ids);
+        let nodes =
+            List::new(nodes).block(Block::default().borders(Borders::ALL)).highlight_symbol(">> ");
+        nodes
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash)]
+pub struct SessionInfoView {
+    pub session_name: String,
+    pub session_id: String,
+    pub parent: String,
+    pub children: Vec<ConnectInfo>,
+}
+
+impl SessionInfoView {
+    pub fn default() -> SessionInfoView {
+        let session_name = String::new();
+        let session_id = String::new();
+        let parent = String::new();
+        let children: Vec<ConnectInfo> = Vec::new();
+        SessionInfoView { session_name, session_id, parent, children }
+    }
+
+    pub fn update(mut self, data: SessionInfo) -> Result<()> {
+        self.session_name = data.session_name;
+        self.session_id = data.session_id;
+        self.parent = data.parent;
+        self.children = data.children;
+        Ok(())
+    }
+
+    pub fn render(self, session: &SessionInfo) {
+        //let mut lines: Vec<Spans> = Vec::new();
+        //let name_span = Span::raw(self.session_name);
+        //let ids = ListItem::new(lines);
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash)]
+pub struct ConnectInfoView {
+    pub connect_id: String,
+    pub addr: String,
+    pub is_empty: bool,
+    pub last_msg: String,
+    pub last_status: String,
+    pub state: String,
+    pub msg_log: Vec<String>,
+    pub parent: String,
+}
+
+impl ConnectInfoView {
+    pub fn default() -> ConnectInfoView {
+        let connect_id = String::new();
+        let addr = String::new();
+        let is_empty = true;
+        let last_msg = String::new();
+        let last_status = String::new();
+        let state = String::new();
+        let msg_log: Vec<String> = Vec::new();
+        let parent = String::new();
+        ConnectInfoView {
+            connect_id,
+            addr,
+            is_empty,
+            last_msg,
+            last_status,
+            state,
+            msg_log,
+            parent,
+        }
+    }
+
+    pub fn update(mut self, data: ConnectInfo) -> Result<()> {
+        self.connect_id = data.connect_id;
+        self.addr = data.addr;
+        self.is_empty = data.is_empty;
+        self.last_msg = data.last_msg;
+        self.last_status = data.last_status;
+        self.state = data.state;
+        self.msg_log = data.msg_log;
+        self.parent = data.parent;
+        Ok(())
+    }
+
+    pub fn render(self, connect: &ConnectInfo) {
+        let mut lines: Vec<Spans> = Vec::new();
+        //let name_span = Span::raw(self.connect_id);
+    }
+}
+
+//#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash)]
+//pub enum SelectableObject {
+//    Node(NodeInfoView),
+//    Session(SessionInfoView),
+//    Connect(ConnectInfoViewView),
+//}
+
+#[derive(Debug, Clone)]
 pub struct IdListView {
     pub state: ListState,
     pub ids: FxHashSet<String>,
@@ -66,7 +289,7 @@ impl IdListView {
     }
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct InfoListView {
     pub index: usize,
     pub infos: FxHashMap<String, SelectableObject>,