Skip to content

Commit

Permalink
Merge pull request #2 from sreedevk/v0.0.4
Browse files Browse the repository at this point in the history
[Version 0.0.4] Ncurses Dependency Replacement + General UX Enhancements
  • Loading branch information
sreedevk authored Dec 20, 2023
2 parents 9a1e20e + 380d9f6 commit 61835bb
Show file tree
Hide file tree
Showing 8 changed files with 431 additions and 389 deletions.
418 changes: 138 additions & 280 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "blaze-ssh"
version = "0.0.3"
version = "0.0.4"
edition = "2021"
authors = ["Sreedev Kodichath <[email protected]>"]
description = "A Configurable CLI tool that helps you ssh into aws ec2 instances without leaving the terminal"
Expand All @@ -22,12 +22,11 @@ anyhow = "1.0.75"
aws-config = { version = "1.0.3", features = ["behavior-version-latest"] }
aws-sdk-ec2 = "1.4.0"
clap = { version = "4.4.11", features = ["derive"] }
cursive = "0.20.0"
fuzzy-matcher = "0.3.7"
crossterm = "0.27.0"
prettytable-rs = "0.10.0"
ratatui = "0.25.0"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
shellexpand = "3.1.0"
slim = "0.1.0"
tokio = { version = "1.34.0", features = ["full"] }
toml = "0.8.8"
10 changes: 8 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use std::path::PathBuf;
pub const CONFIG_PATH: &str = "~/.config/blaze/config.toml";
#[allow(dead_code)]
pub const DEFAULT: &str = r#"
# Rename this section to "config" to use this as your default config
[config.sample]
[config]
private-key = ""
default-user = "ec2-user"
jumphost = ""
Expand Down Expand Up @@ -59,6 +58,13 @@ impl Config {
Ok(())
}

pub fn read_raw(path: Option<PathBuf>) -> Result<String> {
let config_path =
PathBuf::from(shellexpand::tilde(&Self::get_config_path(path)?).to_string());

std::fs::read_to_string(config_path).map_err(|e| e.into())
}

pub fn load(path: Option<PathBuf>) -> Result<Self> {
let config_path =
PathBuf::from(shellexpand::tilde(&Self::get_config_path(path)?).to_string());
Expand Down
12 changes: 10 additions & 2 deletions src/instance_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ impl InstanceDetails {
pub fn display_name(&self) -> Result<String> {
let cloned_instance = self.clone();
Ok(format!(
"{:<32} | priv_ip: {:12} | pub_ip: {:>12} | {:<32}",
cloned_instance.instance_name.unwrap_or("None".to_string()),
"{:<32} | priv_ip: {:>16} | pub_ip: {:>16} | {:<32}",
Self::truncate_string(cloned_instance.instance_name.unwrap_or("None".to_string())),
cloned_instance.private_ip.unwrap_or("None".to_string()),
cloned_instance.public_ip.unwrap_or("None".to_string()),
cloned_instance.instance_id.unwrap_or("None".to_string())
Expand All @@ -123,4 +123,12 @@ impl InstanceDetails {
.cloned()
.and_then(|tag| tag.value)
}

fn truncate_string(input: String) -> String {
if input.len() <= 32 {
return input;
}

format!("{}...", &input[..29])
}
}
6 changes: 5 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ async fn main() -> Result<()> {
let instance = match filtered_instance_set.is_non_selectable() {
true => filtered_instance_set.instances.first().unwrap().clone(),
false => {
let ui = Ui::new(filtered_instance_set)?;
let mut ui = Ui::new(
filtered_instance_set,
config::Config::read_raw(cli.clone().config)?,
)?;

ui.run()?
}
};
Expand Down
100 changes: 0 additions & 100 deletions src/ui.rs

This file was deleted.

200 changes: 200 additions & 0 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
mod stateful_list;

use anyhow::{anyhow, Result};
use stateful_list::StatefulList;

use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};

use ratatui::{
layout::Constraint,
prelude::*,
style::Modifier,
widgets::{Block, Borders, List, ListItem, Padding, Paragraph},
Frame,
};

use std::{io::stdout, time::Duration};

use crate::instance_details::{InstanceDetails, InstanceSet};

#[derive(Debug, Clone)]
enum BlazeUiEvent {
Quit,
Noop,
ListNext,
ListPrevious,
Selected,
}

#[derive(Debug, Clone)]
pub struct Ui {
list: StatefulList<(String, InstanceDetails)>,
config: String,
}

impl Ui {
pub fn new(instance_set: InstanceSet, config: String) -> Result<Self> {
let list_elements = instance_set
.instances
.iter()
.map(|instance| {
(
instance.display_name().unwrap_or_default(),
instance.clone(),
)
})
.collect::<Vec<_>>();

let list = StatefulList::with_items(list_elements);

Ok(Self { list, config })
}

pub fn run(&mut self) -> Result<InstanceDetails> {
/* terminal setup */
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;

/* create app and run it */
loop {
/* Render UI */
terminal.draw(|frame| {
Self::ui(frame, &mut self.list, self.config.clone()).unwrap();
})?;

/* Handle Events */
match self.read_events()? {
BlazeUiEvent::Quit => {
self.list.state.select(None);
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
break;
}
BlazeUiEvent::Noop => {
continue;
}
BlazeUiEvent::ListNext => {
self.list.next();
}
BlazeUiEvent::ListPrevious => {
self.list.previous();
}
BlazeUiEvent::Selected => {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
break;
}
}
}

match self.list.state.selected() {
Some(index) => Ok(self.list.items[index].1.clone()),
None => Err(anyhow!("No instance selected")),
}
}

fn ui(
frame: &mut Frame,
list: &mut StatefulList<(String, InstanceDetails)>,
config: String,
) -> Result<()> {
let slices = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(70), Constraint::Percentage(30)])
.split(frame.size());

let dashboard_slices = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(slices[1]);

/* render keybindings */
let keybindings_list_items = vec![
ListItem::new("j/Down: Next item"),
ListItem::new("k/Up: Previous item"),
ListItem::new("Enter: Select item"),
ListItem::new("q/Esc: Quit"),
];

let keybindings_list = List::new(keybindings_list_items).block(
Block::default()
.borders(Borders::ALL)
.bg(Color::Black)
.fg(Color::White)
.padding(Padding::new(4, 1, 1, 1))
.title("Keybindings"),
);

frame.render_widget(keybindings_list, dashboard_slices[0]);

/* render raw config */
let config = Paragraph::new(config).block(
Block::default()
.borders(Borders::ALL)
.bg(Color::Black)
.fg(Color::White)
.title("Config")
.padding(Padding::new(4, 1, 1, 1)),
);
frame.render_widget(config, dashboard_slices[1]);

/* render instances list */
let prepared_items: Vec<ListItem> = list
.items
.iter()
.map(|(dsp_name, _item)| {
ListItem::new(dsp_name.clone()).style(
Style::default()
.fg(ratatui::style::Color::White)
.bg(ratatui::style::Color::Black),
)
})
.collect();

let prepared_list = List::new(prepared_items)
.block(
Block::default()
.borders(Borders::ALL)
.bg(Color::Black)
.padding(Padding::new(4, 4, 1, 1))
.title("Instances"),
)
.highlight_style(
Style::default()
.bg(ratatui::style::Color::Green)
.fg(ratatui::style::Color::Black)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol(">>= ");

frame.render_stateful_widget(prepared_list, slices[0], &mut list.state);

Ok(())
}

fn read_events(&self) -> Result<BlazeUiEvent> {
match event::poll(Duration::from_millis(250))? {
true => match event::read()? {
Event::Key(event) => match event.kind {
KeyEventKind::Press => match event.code {
KeyCode::Char('q') | KeyCode::Esc => Ok(BlazeUiEvent::Quit),
KeyCode::Char('j') | KeyCode::Down => Ok(BlazeUiEvent::ListNext),
KeyCode::Char('k') | KeyCode::Up => Ok(BlazeUiEvent::ListPrevious),
KeyCode::Enter => Ok(BlazeUiEvent::Selected),
_ => Ok(BlazeUiEvent::Noop),
},
_ => Ok(BlazeUiEvent::Noop),
},
_ => Ok(BlazeUiEvent::Noop),
},
false => Ok(BlazeUiEvent::Noop),
}
}
}
Loading

0 comments on commit 61835bb

Please sign in to comment.