From 6215245adc2fab7f81964b2832dec81077a10e0f Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 17 Nov 2021 21:50:13 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20host=20list=20is=20now=20a=20ta?= =?UTF-8?q?ble?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 4 +-- src/main.rs | 4 +-- src/render_host_list.rs | 60 ++++++++++++++++++++++++----------------- src/ssh_config_store.rs | 6 +++++ 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/app.rs b/src/app.rs index 397d575..7a69848 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use tui::widgets::ListState; +use tui::widgets::TableState; use crate::ssh_config_store::{SshGroup, SshGroupItem}; @@ -9,7 +9,7 @@ pub enum ConfigDisplayMode { pub struct App<'a> { pub selected_group: usize, - pub host_state: ListState, + pub host_state: TableState, pub groups: &'a [SshGroup], pub config_display_mode: ConfigDisplayMode, pub should_quit: bool, diff --git a/src/main.rs b/src/main.rs index 841cfcf..456b681 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::process::Command; use tui::{ layout::{Constraint, Direction, Layout}, - widgets::ListState, + widgets::TableState, }; mod app; @@ -32,7 +32,7 @@ async fn main() -> Result<(), Box> { selected_group: 0, config_paragraph_offset: 0, groups: &scs.groups, - host_state: ListState::default(), + host_state: TableState::default(), should_quit: false, should_spawn_ssh: false, config_display_mode: ConfigDisplayMode::Selected, diff --git a/src/render_host_list.rs b/src/render_host_list.rs index 0aab72b..00fa505 100644 --- a/src/render_host_list.rs +++ b/src/render_host_list.rs @@ -2,10 +2,10 @@ use std::io::Stdout; use tui::{ backend::CrosstermBackend, - layout::Rect, - style::{Color, Modifier, Style}, - text::{Span, Spans}, - widgets::{Block, Borders, List, ListItem}, + layout::{Constraint, Rect}, + style::{Color, Style}, + text::Span, + widgets::{Block, Borders, Cell, Row, Table}, Frame, }; @@ -19,26 +19,38 @@ pub fn render_host_list(app: &mut App, area: Rect, frame: &mut Frame = app.groups[app.selected_group] - .items + let normal_style = Style::default(); + let selected_style = Style::default().fg(Color::Magenta); + + let header_cells = ["Host", "Last Used", "Nb Connection"] .iter() - .map(|i| { - ListItem::new(Spans::from(Span::styled( - i.name.to_string(), - Style::default(), - ))) - .style(Style::default().fg(Color::White)) - }) - .collect(); - - let items = List::new(items) + .map(|h| Cell::from(*h).style(Style::default().fg(Color::White))); + + let header = Row::new(header_cells) + .style(normal_style) + .height(1) + .bottom_margin(1); + + let rows = app.get_selected_group().items.iter().map(|item| { + let cells = [ + Cell::from(item.name.to_string()).style(normal_style), + Cell::from(item.last_used.to_string()).style(normal_style), + Cell::from(item.connection_count.to_string()).style(normal_style), + ]; + + Row::new(cells).height(1).bottom_margin(1) + }); + + let t = Table::new(rows) + .header(header) .block(block) - .highlight_style( - Style::default() - .fg(Color::Magenta) - .add_modifier(Modifier::BOLD), - ) - .highlight_symbol("> "); - - frame.render_stateful_widget(items, area, &mut app.host_state); + .highlight_style(selected_style) + .highlight_symbol(">> ") + .widths(&[ + Constraint::Percentage(30), + Constraint::Percentage(30), + Constraint::Percentage(30), + ]); + + frame.render_stateful_widget(t, area, &mut app.host_state); } diff --git a/src/ssh_config_store.rs b/src/ssh_config_store.rs index cb5b205..92bd218 100644 --- a/src/ssh_config_store.rs +++ b/src/ssh_config_store.rs @@ -6,6 +6,8 @@ use ssh_cfg::{SshConfig, SshConfigParser, SshHostConfig}; pub struct SshGroupItem { pub name: String, pub full_name: String, + pub connection_count: i64, + pub last_used: String, pub host_config: SshHostConfig, } @@ -51,6 +53,8 @@ impl SshConfigStore { let group_item = SshGroupItem { name: group_key, + connection_count: key.len() as i64, + last_used: "A".to_string(), full_name: key.to_string(), host_config: value.clone(), }; @@ -72,6 +76,8 @@ impl SshConfigStore { groups[0].items.push(SshGroupItem { full_name: key.to_string(), + connection_count: key.len() as i64, + last_used: "B".to_string(), name: key.to_string(), host_config: value.clone(), }); From c00a48f5700686dece0be287ccc3d1b9c0f5a61e Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 18 Nov 2021 00:15:46 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20added=20local=20db=20logic=20fo?= =?UTF-8?q?r=20storing=20connections=20count=20and=20last=20connection=20d?= =?UTF-8?q?ate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 202 +++++++++++++++++- Cargo.toml | 4 + README.md | 23 +- src/app.rs | 43 +++- src/database.rs | 60 ++++++ src/input_handler.rs | 6 +- src/main.rs | 46 ++-- src/render_config.rs | 2 +- src/render_group_tabs.rs | 1 + ...nder_host_list.rs => render_host_table.rs} | 19 +- src/ssh_config_store.rs | 20 +- 11 files changed, 366 insertions(+), 60 deletions(-) create mode 100644 src/database.rs rename src/{render_host_list.rs => render_host_table.rs} (72%) diff --git a/Cargo.lock b/Cargo.lock index 683a76e..f808e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.3.2" @@ -95,6 +101,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "concurrent-queue" version = "1.2.2" @@ -163,6 +182,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.3.6" @@ -184,8 +212,12 @@ checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" name = "fast-ssh" version = "0.0.2" dependencies = [ + "chrono", "crossterm 0.22.1", + "dirs 4.0.0", "futures", + "rustbreak", + "serde", "ssh_cfg", "tokio", "tui", @@ -415,6 +447,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -480,9 +531,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1b940aa8e0562ece01eb12a9731bb8f6f0325c2c97c8629f852504f01d4537" dependencies = [ - "dirs", + "dirs 3.0.2", ] +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -513,6 +570,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -532,12 +629,64 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ron" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86018df177b1beef6c7c8ef949969c4f7cb9a9344181b92486b23c79995bdaa4" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rustbreak" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460d97902465327d69ecfe8cefdb5972c6f94d6127ac9e992acdb51458bebc27" +dependencies = [ + "ron", + "serde", + "tempfile", + "thiserror", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook" version = "0.3.10" @@ -587,7 +736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ed15ba6be9ba5e57c29f318c7734e12a56e0495e41a58d0eaba7742d10aef2" dependencies = [ "async-fs", - "dirs", + "dirs 3.0.2", "indexmap", "plain_path", ] @@ -603,6 +752,51 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "tokio" version = "1.14.0" @@ -673,9 +867,9 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "winapi" diff --git a/Cargo.toml b/Cargo.toml index ba35c6b..6b45df5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,7 @@ crossterm = "0.22.1" ssh_cfg = "0.1" futures = "0.3" tokio = { version = "1", features = ["full"] } +rustbreak = { version = "2.0.0", features = ["ron_enc"] } +dirs = "4.0.0" +serde = "1.0.130" +chrono = "0.4.19" \ No newline at end of file diff --git a/README.md b/README.md index 7f9bc0a..78cb5c7 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Connect quickly to your services 🚀

- + + + @@ -19,16 +19,23 @@
-![](https://i.imgur.com/CwHDIiR.png) +![](https://i.imgur.com/pVf2hES.png) # Documentation If you already have an SSH configuration file you don't have to add anything, Fast-SSH just parses this file and displays it. Fast-SSH has a group system. This allows you to sort your servers, for example, by project, mission or client. -To make groups, it's simple, just define your `Host` as `Group/ServerName` ( see full configuration in above picture ). Now your groups will be displayed in FastSSH. You can now select a group and display only the servers defined in that group. +To make some groups, it's simple, just define your `Host` as `Group/ServerName` ( see full configuration in above picture ) and your groups will be displayed in FastSSH. You can now select a group and display only the servers defined in that group. Now all you have to do is launch Fast-SSH, select your service and press enter to connect. -# Usage -Download the latest release for your platform [here](https://github.com/Julien-R44/cli-candlestick-chart/releases) and put in directory in your PATH. -Then you can launch Fast-SSH with `fast-ssh`. +## File Database +A file database is stored at ~/.fastssh/db.ron. This file is automatically created when you launch Fast-SSH. +This database is used to store the number of connections to a service and the date of last connection. + +# Installation +Download the latest release for your platform [here](https://github.com/Julien-R44/cli-candlestick-chart/releases) and put in directory in your PATH. ( Packages managers coming soon ) + +If you use cargo you can run `cargo install fast-ssh` + +Then you can launch Fast-SSH with `fast-ssh`. diff --git a/src/app.rs b/src/app.rs index 7a69848..dc29fe0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,25 +1,58 @@ +use std::fs; use tui::widgets::TableState; -use crate::ssh_config_store::{SshGroup, SshGroupItem}; +use crate::{ + database::FileDatabase, + ssh_config_store::{SshConfigStore, SshGroup, SshGroupItem}, +}; pub enum ConfigDisplayMode { Global, Selected, } -pub struct App<'a> { +pub struct App { pub selected_group: usize, pub host_state: TableState, - pub groups: &'a [SshGroup], + pub scs: SshConfigStore, pub config_display_mode: ConfigDisplayMode, pub should_quit: bool, pub should_spawn_ssh: bool, pub config_paragraph_offset: u16, + pub db: FileDatabase, } -impl App<'_> { +impl App { + pub async fn new() -> Result> { + let db = App::create_or_get_db_file()?; + let scs = SshConfigStore::new(&db).await?; + + Ok(App { + selected_group: 0, + config_paragraph_offset: 0, + scs, + host_state: TableState::default(), + should_quit: false, + should_spawn_ssh: false, + config_display_mode: ConfigDisplayMode::Selected, + db, + }) + } + + pub fn create_or_get_db_file() -> Result> { + if let Some(home) = dirs::home_dir() { + let conf_path = home.join(".fast-ssh"); + let db_path = conf_path.join("db.ron"); + + fs::create_dir_all(&conf_path)?; + return FileDatabase::new(db_path.to_str().unwrap()); + } + + Err("Could not find home directory".into()) + } + pub fn get_selected_group(&self) -> &SshGroup { - &self.groups[self.selected_group] + &self.scs.groups[self.selected_group] } pub fn get_selected_config(&self) -> Option<&SshGroupItem> { diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..5a9ed72 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,60 @@ +use rustbreak::{deser::Ron, FileDatabase as _FileDatabase, RustbreakError}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +pub struct FileDatabase { + db: _FileDatabase, Ron>, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub struct HostDatabaseEntry { + pub connection_count: i64, + pub last_used_date: i64, +} + +impl FileDatabase { + pub fn new(filename: &str) -> Result> { + let db = + _FileDatabase::, Ron>::load_from_path_or_default( + filename, + )?; + + db.load()?; + Ok(FileDatabase { db }) + } + + pub fn get_host_values(&self, host_key: &str) -> Result { + self.db.read(|db| { + let key_value = db.get_key_value(host_key); + + if let Some(value) = key_value { + *value.1 + } else { + HostDatabaseEntry { + connection_count: 0, + last_used_date: 0, + } + } + }) + } + + pub fn save_host_values( + &self, + host_key: &str, + connection_count: i64, + last_used_date: i64, + ) -> Result<(), RustbreakError> { + self.db.write(|db| { + db.insert( + host_key.to_owned(), + HostDatabaseEntry { + connection_count, + last_used_date, + }, + ); + })?; + + self.db.save()?; + Ok(()) + } +} diff --git a/src/input_handler.rs b/src/input_handler.rs index 8eb9f24..c9817c6 100644 --- a/src/input_handler.rs +++ b/src/input_handler.rs @@ -6,10 +6,10 @@ pub fn handle_inputs(app: &mut App) -> Result<(), Box> { if let Event::Key(key) = event::read()? { match key.code { KeyCode::Tab => { - app.selected_group = (app.selected_group + 1) % app.groups.len(); + app.selected_group = (app.selected_group + 1) % app.scs.groups.len(); } KeyCode::Down => { - let items = &app.groups[app.selected_group].items; + let items = &app.scs.groups[app.selected_group].items; let i = match app.host_state.selected() { Some(i) => { if i >= items.len() - 1 { @@ -23,7 +23,7 @@ pub fn handle_inputs(app: &mut App) -> Result<(), Box> { app.host_state.select(Some(i)); } KeyCode::Up => { - let items = &app.groups[app.selected_group].items; + let items = &app.scs.groups[app.selected_group].items; let i = match app.host_state.selected() { Some(i) => { if i == 0 { diff --git a/src/main.rs b/src/main.rs index 456b681..6f14240 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,42 +1,29 @@ use std::process::Command; -use tui::{ - layout::{Constraint, Direction, Layout}, - widgets::TableState, -}; +use tui::layout::{Constraint, Direction, Layout}; mod app; +mod database; mod input_handler; mod render_config; mod render_group_tabs; -mod render_host_list; +mod render_host_table; mod render_shortcuts; mod ssh_config_store; mod term; + use app::*; use input_handler::*; use render_config::*; use render_group_tabs::*; -use render_host_list::*; +use render_host_table::*; use render_shortcuts::*; -use ssh_config_store::*; use term::*; #[tokio::main] async fn main() -> Result<(), Box> { - let scs = SshConfigStore::new().await?; - let mut terminal = init_terminal()?; - - let mut app = App { - selected_group: 0, - config_paragraph_offset: 0, - groups: &scs.groups, - host_state: TableState::default(), - should_quit: false, - should_spawn_ssh: false, - config_display_mode: ConfigDisplayMode::Selected, - }; + let mut app = App::new().await?; app.host_state.select(Some(0)); @@ -57,16 +44,16 @@ async fn main() -> Result<(), Box> { [ Constraint::Percentage(40), Constraint::Length(2), - Constraint::Percentage(40), + Constraint::Percentage(30), Constraint::Length(2), - Constraint::Percentage(20), + Constraint::Percentage(30), ] .as_ref(), ) .split(chunks[1]); render_group_tabs(&app, chunks[0], frame); - render_host_list(&mut app, chunk_b[0], frame); + render_host_table(&mut app, chunk_b[0], frame); render_config(&mut app, chunk_b[2], frame); render_shortcuts(&app, chunk_b[4], frame); })?; @@ -81,11 +68,16 @@ async fn main() -> Result<(), Box> { restore_terminal(&mut terminal)?; if app.should_spawn_ssh { - // TODO: store full host in app - Command::new("ssh") - .arg(&app.get_selected_config().unwrap().full_name) - .spawn()? - .wait()?; + let selected_config = app.get_selected_config().unwrap(); + let host_name = &selected_config.full_name; + + app.db.save_host_values( + host_name, + selected_config.connection_count + 1, + chrono::offset::Local::now().timestamp(), + )?; + + Command::new("ssh").arg(host_name).spawn()?.wait()?; } Ok(()) diff --git a/src/render_config.rs b/src/render_config.rs index 43ddc01..b9d6408 100644 --- a/src/render_config.rs +++ b/src/render_config.rs @@ -32,7 +32,7 @@ pub fn render_config(app: &mut App, area: Rect, frame: &mut Frame(app: &'a App, block: Block<'a>) -> Paragraph<'a> { - let spans: Vec = app.groups.iter().fold(vec![], |mut acc, group| { + let spans: Vec = app.scs.groups.iter().fold(vec![], |mut acc, group| { let new_spans: Vec = group .items .iter() diff --git a/src/render_group_tabs.rs b/src/render_group_tabs.rs index 24656b2..ad14993 100644 --- a/src/render_group_tabs.rs +++ b/src/render_group_tabs.rs @@ -17,6 +17,7 @@ pub fn render_group_tabs(app: &App, area: Rect, frame: &mut Frame>) { +pub fn render_host_table(app: &mut App, area: Rect, frame: &mut Frame>) { let block = Block::default() .borders(Borders::ALL) .border_style(Style::default().fg(Color::LightMagenta)) @@ -32,9 +36,16 @@ pub fn render_host_list(app: &mut App, area: Rect, frame: &mut Frame::from(d); + timestamp_str = dt.format("%D %R").to_string(); + } + let cells = [ Cell::from(item.name.to_string()).style(normal_style), - Cell::from(item.last_used.to_string()).style(normal_style), + Cell::from(timestamp_str).style(normal_style), Cell::from(item.connection_count.to_string()).style(normal_style), ]; @@ -48,7 +59,7 @@ pub fn render_host_list(app: &mut App, area: Rect, frame: &mut Frame> ") .widths(&[ Constraint::Percentage(30), - Constraint::Percentage(30), + Constraint::Percentage(40), Constraint::Percentage(30), ]); diff --git a/src/ssh_config_store.rs b/src/ssh_config_store.rs index 92bd218..4705631 100644 --- a/src/ssh_config_store.rs +++ b/src/ssh_config_store.rs @@ -2,12 +2,14 @@ use std::{error::Error, fmt::Debug}; use ssh_cfg::{SshConfig, SshConfigParser, SshHostConfig}; +use crate::database::FileDatabase; + #[derive(Debug)] pub struct SshGroupItem { pub name: String, pub full_name: String, pub connection_count: i64, - pub last_used: String, + pub last_used: i64, pub host_config: SshHostConfig, } @@ -24,7 +26,7 @@ pub struct SshConfigStore { } impl SshConfigStore { - pub async fn new() -> Result> { + pub async fn new(db: &FileDatabase) -> Result> { let ssh_config = SshConfigParser::parse_home() .await .expect("Failed to parse ~/.ssh/config"); @@ -34,17 +36,19 @@ impl SshConfigStore { groups: Vec::new(), }; - scs.create_ssh_groups(); + scs.create_ssh_groups(db); Ok(scs) } - pub fn create_ssh_groups(&mut self) { + fn create_ssh_groups(&mut self, db: &FileDatabase) { let mut groups: Vec = vec![SshGroup { name: "Others".to_string(), items: Vec::new(), }]; self.config.iter().for_each(|(key, value)| { + let host_entry = db.get_host_values(key).unwrap(); + if key.contains('/') { let group_name = key.split('/').next().unwrap(); let group_key = key.split('/').skip(1).collect::>().join(""); @@ -53,8 +57,8 @@ impl SshConfigStore { let group_item = SshGroupItem { name: group_key, - connection_count: key.len() as i64, - last_used: "A".to_string(), + connection_count: host_entry.connection_count, + last_used: host_entry.last_used_date, full_name: key.to_string(), host_config: value.clone(), }; @@ -76,8 +80,8 @@ impl SshConfigStore { groups[0].items.push(SshGroupItem { full_name: key.to_string(), - connection_count: key.len() as i64, - last_used: "B".to_string(), + connection_count: host_entry.connection_count, + last_used: host_entry.last_used_date, name: key.to_string(), host_config: value.clone(), });