From 28242ceec0184c78aac050355a53938ed788ca67 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Tue, 22 Jun 2021 02:27:10 +0900 Subject: [PATCH] use config conn --- Cargo.toml | 2 +- src/app.rs | 63 ++++++++++++++++++++++++++--------- src/handlers/database_list.rs | 2 +- src/handlers/mod.rs | 9 +++-- src/handlers/record_table.rs | 2 +- src/handlers/table_list.rs | 2 +- src/main.rs | 16 ++++++--- src/ui/mod.rs | 50 +++++++++++++++++++++++++-- src/user_config.rs | 26 ++++++++++++--- src/utils.rs | 16 +++------ 10 files changed, 143 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48883ef..41e2383 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1.0.38" unicode-width = "0.1" sqlx = { version = "0.4.1", features = ["mysql", "chrono", "runtime-tokio-rustls"] } chrono = "0.4" -tokio = { version = "0.2", features = ["full"] } +tokio = { version = "0.2.22", features = ["full"] } futures = "0.3.5" serde_json = "1.0" serde = "1.0" diff --git a/src/app.rs b/src/app.rs index 504491f..a5d86d4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,5 @@ +use crate::{user_config::UserConfig, utils::get_tables}; use sqlx::mysql::MySqlPool; -use sqlx::Row; use tui::widgets::{ListState, TableState}; pub enum InputMode { @@ -11,6 +11,7 @@ pub enum FocusType { Dabatases(bool), Tables(bool), Records(bool), + Connections, } #[derive(Clone)] @@ -89,16 +90,10 @@ impl RecordTable { impl Database { pub async fn new(name: String, pool: &MySqlPool) -> anyhow::Result { - let tables = sqlx::query(format!("show tables from {}", name).as_str()) - .fetch_all(pool) - .await? - .iter() - .map(|table| Table { name: table.get(0) }) - .collect::>(); Ok(Self { selected_table: ListState::default(), - name, - tables, + name: name.clone(), + tables: get_tables(name, pool).await?, }) } @@ -131,7 +126,7 @@ impl Database { } } -pub struct App { +pub struct App<'a> { pub input: String, pub input_mode: InputMode, pub messages: Vec>, @@ -139,10 +134,13 @@ pub struct App { pub databases: Vec, pub record_table: RecordTable, pub focus_type: FocusType, + pub user_config: Option, + pub selected_connection: ListState, + pub pool: Option<&'a MySqlPool>, } -impl Default for App { - fn default() -> App { +impl<'a> Default for App<'a> { + fn default() -> App<'a> { App { input: String::new(), input_mode: InputMode::Normal, @@ -151,12 +149,15 @@ impl Default for App { databases: Vec::new(), record_table: RecordTable::default(), focus_type: FocusType::Dabatases(false), + user_config: None, + selected_connection: ListState::default(), + pool: None, } } } -impl App { - pub fn next(&mut self) { +impl<'a> App<'a> { + pub fn next_database(&mut self) { let i = match self.selected_database.selected() { Some(i) => { if i >= self.databases.len() - 1 { @@ -170,7 +171,7 @@ impl App { self.selected_database.select(Some(i)); } - pub fn previous(&mut self) { + pub fn previous_database(&mut self) { let i = match self.selected_database.selected() { Some(i) => { if i == 0 { @@ -183,4 +184,36 @@ impl App { }; self.selected_database.select(Some(i)); } + + pub fn next_connection(&mut self) { + if let Some(config) = &self.user_config { + let i = match self.selected_connection.selected() { + Some(i) => { + if i >= config.connections.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.selected_connection.select(Some(i)); + } + } + + pub fn previous_connection(&mut self) { + if let Some(config) = &self.user_config { + let i = match self.selected_database.selected() { + Some(i) => { + if i == 0 { + config.connections.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.selected_connection.select(Some(i)); + } + } } diff --git a/src/handlers/database_list.rs b/src/handlers/database_list.rs index c7d40a7..9747d12 100644 --- a/src/handlers/database_list.rs +++ b/src/handlers/database_list.rs @@ -3,7 +3,7 @@ use crate::event::Key; use sqlx::mysql::MySqlPool; use sqlx::Row; -pub async fn handler(key: Key, app: &mut App, pool: &MySqlPool) -> anyhow::Result<()> { +pub async fn handler<'a>(key: Key, app: &mut App<'a>, pool: &MySqlPool) -> anyhow::Result<()> { let databases = sqlx::query("show databases") .fetch_all(pool) .await? diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 120be81..b3b0d76 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -8,7 +8,7 @@ use futures::TryStreamExt; use sqlx::mysql::MySqlPool; use sqlx::{Column, Executor, Row, TypeInfo}; -pub async fn handle_app(key: Key, app: &mut App, pool: &MySqlPool) -> anyhow::Result<()> { +pub async fn handle_app<'a>(key: Key, app: &mut App<'a>, pool: &MySqlPool) -> anyhow::Result<()> { match app.input_mode { InputMode::Normal => match key { Key::Char('e') => { @@ -35,8 +35,9 @@ pub async fn handle_app(key: Key, app: &mut App, pool: &MySqlPool) -> anyhow::Re _ => (), }, Key::Up => match app.focus_type { + FocusType::Connections => app.previous_connection(), FocusType::Records(true) => app.record_table.previous(), - FocusType::Dabatases(true) => app.previous(), + FocusType::Dabatases(true) => app.previous_database(), FocusType::Tables(true) => match app.selected_database.selected() { Some(index) => { app.record_table.column_index = 0; @@ -56,8 +57,9 @@ pub async fn handle_app(key: Key, app: &mut App, pool: &MySqlPool) -> anyhow::Re _ => (), }, Key::Down => match app.focus_type { + FocusType::Connections => app.next_connection(), FocusType::Records(true) => app.record_table.next(), - FocusType::Dabatases(true) => app.next(), + FocusType::Dabatases(true) => app.next_database(), FocusType::Tables(true) => match app.selected_database.selected() { Some(index) => { app.record_table.column_index = 0; @@ -77,6 +79,7 @@ pub async fn handle_app(key: Key, app: &mut App, pool: &MySqlPool) -> anyhow::Re _ => (), }, Key::Enter => match &app.focus_type { + FocusType::Connections => app.focus_type = FocusType::Dabatases(true), FocusType::Records(false) => app.focus_type = FocusType::Records(true), FocusType::Dabatases(false) => app.focus_type = FocusType::Dabatases(true), FocusType::Tables(false) => app.focus_type = FocusType::Tables(true), diff --git a/src/handlers/record_table.rs b/src/handlers/record_table.rs index db7c51b..df3c55a 100644 --- a/src/handlers/record_table.rs +++ b/src/handlers/record_table.rs @@ -2,6 +2,6 @@ use crate::app::App; use crate::event::Key; use sqlx::mysql::MySqlPool; -pub async fn handler(key: Key, app: &mut App, pool: &MySqlPool) -> anyhow::Result<()> { +pub async fn handler<'a>(key: Key, app: &mut App<'a>, pool: &MySqlPool) -> anyhow::Result<()> { Ok(()) } diff --git a/src/handlers/table_list.rs b/src/handlers/table_list.rs index ed238c7..f72c450 100644 --- a/src/handlers/table_list.rs +++ b/src/handlers/table_list.rs @@ -4,7 +4,7 @@ use futures::TryStreamExt; use sqlx::mysql::MySqlPool; use sqlx::{Column, Executor, Row, TypeInfo}; -pub async fn handler(key: Key, app: &mut App, pool: &MySqlPool) -> anyhow::Result<()> { +pub async fn handler<'a>(key: Key, app: &mut App<'a>, pool: &MySqlPool) -> anyhow::Result<()> { match app.selected_database.selected() { Some(index) => { &app.databases[index].next(); diff --git a/src/main.rs b/src/main.rs index 1fb6c61..3fbc793 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,13 +44,20 @@ async fn main() -> anyhow::Result<()> { let events = event::Events::new(250); let mut app = &mut app::App::default(); - let hoge = &config.conn.unwrap()["sample"]; + app.user_config = Some(config); + let conn = &app + .user_config + .as_ref() + .unwrap() + .connections + .get(0) + .unwrap(); let pool = MySqlPool::connect( format!( "mysql://{user}:@{host}:{port}", - user = hoge.user, - host = hoge.host, - port = hoge.port + user = conn.user, + host = conn.host, + port = conn.port ) .as_str(), ) @@ -66,6 +73,7 @@ async fn main() -> anyhow::Result<()> { app.record_table.rows = records; app.record_table.headers = headers; app.selected_database.select(Some(0)); + app.focus_type = FocusType::Connections; terminal.clear()?; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 75b13d8..5cb8324 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -8,14 +8,60 @@ use tui::{ text::{Span, Spans, Text}, widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle}, widgets::{ - Axis, BarChart, Block, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List, ListItem, - Paragraph, Row, Sparkline, Table, Tabs, Wrap, + Axis, BarChart, Block, Borders, Cell, Chart, Clear, Dataset, Gauge, LineGauge, List, + ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap, }, Frame, }; use unicode_width::UnicodeWidthStr; pub fn draw(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<()> { + if let FocusType::Connections = app.focus_type { + let percent_x = 60; + let percent_y = 50; + let conns = &app.user_config.as_ref().unwrap().connections; + let connections: Vec = conns + .iter() + .map(|i| { + ListItem::new(vec![Spans::from(Span::raw(&i.name))]) + .style(Style::default().fg(Color::White)) + }) + .collect(); + let tasks = List::new(connections) + .block(Block::default().borders(Borders::ALL).title("Connections")) + .highlight_style(Style::default().fg(Color::Green)) + .style(match app.focus_type { + FocusType::Connections => Style::default().fg(Color::Green), + _ => Style::default(), + }); + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ] + .as_ref(), + ) + .split(f.size()); + + let area = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ] + .as_ref(), + ) + .split(popup_layout[1])[1]; + f.render_widget(Clear, area); + f.render_stateful_widget(tasks, area, &mut app.selected_connection); + return Ok(()); + } + let main_chunks = Layout::default() .direction(Direction::Vertical) .margin(2) diff --git a/src/user_config.rs b/src/user_config.rs index ad93d34..8a50089 100644 --- a/src/user_config.rs +++ b/src/user_config.rs @@ -3,14 +3,14 @@ use std::collections::HashMap; use std::fs::File; use std::io::{BufReader, Read}; -#[derive(Debug, Deserialize)] +#[derive(Debug)] pub struct UserConfig { - pub conn: Option>, + pub connections: Vec, } #[derive(Debug, Deserialize)] pub struct Connection { - pub name: Option, + pub name: String, pub user: String, pub host: String, pub port: u64, @@ -18,14 +18,30 @@ pub struct Connection { impl UserConfig { pub fn new(path: &str) -> anyhow::Result { + #[derive(Debug, Deserialize)] + pub struct ConfigFormat { + pub conn: HashMap, + } + let file = File::open(path)?; let mut buf_reader = BufReader::new(file); let mut contents = String::new(); buf_reader.read_to_string(&mut contents)?; - let config: Result = toml::from_str(&contents); + let config: Result = toml::from_str(&contents); match config { - Ok(config) => Ok(config), + Ok(config) => Ok(UserConfig { + connections: config + .conn + .iter() + .map(|(name, conn)| Connection { + name: name.to_string(), + user: conn.user.to_string(), + host: conn.host.to_string(), + port: conn.port, + }) + .collect::>(), + }), Err(e) => panic!("fail to parse config file: {}", e), } } diff --git a/src/utils.rs b/src/utils.rs index 5629dcc..2cfd18e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -19,7 +19,7 @@ pub async fn get_databases(pool: &MySqlPool) -> anyhow::Result> { } pub async fn get_tables(database: String, pool: &MySqlPool) -> anyhow::Result> { - let tables = sqlx::query(format!("show tables from {}", database).as_str()) + let tables = sqlx::query(format!("show tables from `{}`", database).as_str()) .fetch_all(pool) .await? .iter() @@ -34,11 +34,11 @@ pub async fn get_records( pool: &MySqlPool, ) -> anyhow::Result<(Vec, Vec>)> { &pool - .execute(format!("use {}", database.name).as_str()) + .execute(format!("use `{}`", database.name).as_str()) .await?; - let table_name = format!("SELECT * FROM {}", table.name); + let table_name = format!("SELECT * FROM `{}`", table.name); let mut rows = sqlx::query(table_name.as_str()).fetch(pool); - let headers = sqlx::query(format!("desc {}", table.name).as_str()) + let headers = sqlx::query(format!("desc `{}`", table.name).as_str()) .fetch_all(pool) .await? .iter() @@ -93,14 +93,6 @@ pub async fn get_records( let value: String = row.try_get(col_name).unwrap_or("".to_string()); row_vec.push(value); } - // DATE - // VARCHAR - // INT UNSIGNED - // VARCHAR - // ENUM - // ENUM - // VARCHAR - // BOOLEAN _ => (), } }