use config conn

pull/1/head
Takayuki Maeda 3 years ago
parent 2df25ec7e0
commit 28242ceec0

@ -11,7 +11,7 @@ anyhow = "1.0.38"
unicode-width = "0.1" unicode-width = "0.1"
sqlx = { version = "0.4.1", features = ["mysql", "chrono", "runtime-tokio-rustls"] } sqlx = { version = "0.4.1", features = ["mysql", "chrono", "runtime-tokio-rustls"] }
chrono = "0.4" chrono = "0.4"
tokio = { version = "0.2", features = ["full"] } tokio = { version = "0.2.22", features = ["full"] }
futures = "0.3.5" futures = "0.3.5"
serde_json = "1.0" serde_json = "1.0"
serde = "1.0" serde = "1.0"

@ -1,5 +1,5 @@
use crate::{user_config::UserConfig, utils::get_tables};
use sqlx::mysql::MySqlPool; use sqlx::mysql::MySqlPool;
use sqlx::Row;
use tui::widgets::{ListState, TableState}; use tui::widgets::{ListState, TableState};
pub enum InputMode { pub enum InputMode {
@ -11,6 +11,7 @@ pub enum FocusType {
Dabatases(bool), Dabatases(bool),
Tables(bool), Tables(bool),
Records(bool), Records(bool),
Connections,
} }
#[derive(Clone)] #[derive(Clone)]
@ -89,16 +90,10 @@ impl RecordTable {
impl Database { impl Database {
pub async fn new(name: String, pool: &MySqlPool) -> anyhow::Result<Self> { pub async fn new(name: String, pool: &MySqlPool) -> anyhow::Result<Self> {
let tables = sqlx::query(format!("show tables from {}", name).as_str())
.fetch_all(pool)
.await?
.iter()
.map(|table| Table { name: table.get(0) })
.collect::<Vec<Table>>();
Ok(Self { Ok(Self {
selected_table: ListState::default(), selected_table: ListState::default(),
name, name: name.clone(),
tables, tables: get_tables(name, pool).await?,
}) })
} }
@ -131,7 +126,7 @@ impl Database {
} }
} }
pub struct App { pub struct App<'a> {
pub input: String, pub input: String,
pub input_mode: InputMode, pub input_mode: InputMode,
pub messages: Vec<Vec<String>>, pub messages: Vec<Vec<String>>,
@ -139,10 +134,13 @@ pub struct App {
pub databases: Vec<Database>, pub databases: Vec<Database>,
pub record_table: RecordTable, pub record_table: RecordTable,
pub focus_type: FocusType, pub focus_type: FocusType,
pub user_config: Option<UserConfig>,
pub selected_connection: ListState,
pub pool: Option<&'a MySqlPool>,
} }
impl Default for App { impl<'a> Default for App<'a> {
fn default() -> App { fn default() -> App<'a> {
App { App {
input: String::new(), input: String::new(),
input_mode: InputMode::Normal, input_mode: InputMode::Normal,
@ -151,12 +149,15 @@ impl Default for App {
databases: Vec::new(), databases: Vec::new(),
record_table: RecordTable::default(), record_table: RecordTable::default(),
focus_type: FocusType::Dabatases(false), focus_type: FocusType::Dabatases(false),
user_config: None,
selected_connection: ListState::default(),
pool: None,
} }
} }
} }
impl App { impl<'a> App<'a> {
pub fn next(&mut self) { pub fn next_database(&mut self) {
let i = match self.selected_database.selected() { let i = match self.selected_database.selected() {
Some(i) => { Some(i) => {
if i >= self.databases.len() - 1 { if i >= self.databases.len() - 1 {
@ -170,7 +171,7 @@ impl App {
self.selected_database.select(Some(i)); self.selected_database.select(Some(i));
} }
pub fn previous(&mut self) { pub fn previous_database(&mut self) {
let i = match self.selected_database.selected() { let i = match self.selected_database.selected() {
Some(i) => { Some(i) => {
if i == 0 { if i == 0 {
@ -183,4 +184,36 @@ impl App {
}; };
self.selected_database.select(Some(i)); 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));
}
}
} }

@ -3,7 +3,7 @@ use crate::event::Key;
use sqlx::mysql::MySqlPool; use sqlx::mysql::MySqlPool;
use sqlx::Row; 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") let databases = sqlx::query("show databases")
.fetch_all(pool) .fetch_all(pool)
.await? .await?

@ -8,7 +8,7 @@ use futures::TryStreamExt;
use sqlx::mysql::MySqlPool; use sqlx::mysql::MySqlPool;
use sqlx::{Column, Executor, Row, TypeInfo}; 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 { match app.input_mode {
InputMode::Normal => match key { InputMode::Normal => match key {
Key::Char('e') => { 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 { Key::Up => match app.focus_type {
FocusType::Connections => app.previous_connection(),
FocusType::Records(true) => app.record_table.previous(), 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() { FocusType::Tables(true) => match app.selected_database.selected() {
Some(index) => { Some(index) => {
app.record_table.column_index = 0; 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 { Key::Down => match app.focus_type {
FocusType::Connections => app.next_connection(),
FocusType::Records(true) => app.record_table.next(), 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() { FocusType::Tables(true) => match app.selected_database.selected() {
Some(index) => { Some(index) => {
app.record_table.column_index = 0; 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 { Key::Enter => match &app.focus_type {
FocusType::Connections => app.focus_type = FocusType::Dabatases(true),
FocusType::Records(false) => app.focus_type = FocusType::Records(true), FocusType::Records(false) => app.focus_type = FocusType::Records(true),
FocusType::Dabatases(false) => app.focus_type = FocusType::Dabatases(true), FocusType::Dabatases(false) => app.focus_type = FocusType::Dabatases(true),
FocusType::Tables(false) => app.focus_type = FocusType::Tables(true), FocusType::Tables(false) => app.focus_type = FocusType::Tables(true),

@ -2,6 +2,6 @@ use crate::app::App;
use crate::event::Key; use crate::event::Key;
use sqlx::mysql::MySqlPool; 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(()) Ok(())
} }

@ -4,7 +4,7 @@ use futures::TryStreamExt;
use sqlx::mysql::MySqlPool; use sqlx::mysql::MySqlPool;
use sqlx::{Column, Executor, Row, TypeInfo}; 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() { match app.selected_database.selected() {
Some(index) => { Some(index) => {
&app.databases[index].next(); &app.databases[index].next();

@ -44,13 +44,20 @@ async fn main() -> anyhow::Result<()> {
let events = event::Events::new(250); let events = event::Events::new(250);
let mut app = &mut app::App::default(); 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( let pool = MySqlPool::connect(
format!( format!(
"mysql://{user}:@{host}:{port}", "mysql://{user}:@{host}:{port}",
user = hoge.user, user = conn.user,
host = hoge.host, host = conn.host,
port = hoge.port port = conn.port
) )
.as_str(), .as_str(),
) )
@ -66,6 +73,7 @@ async fn main() -> anyhow::Result<()> {
app.record_table.rows = records; app.record_table.rows = records;
app.record_table.headers = headers; app.record_table.headers = headers;
app.selected_database.select(Some(0)); app.selected_database.select(Some(0));
app.focus_type = FocusType::Connections;
terminal.clear()?; terminal.clear()?;

@ -8,14 +8,60 @@ use tui::{
text::{Span, Spans, Text}, text::{Span, Spans, Text},
widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle}, widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle},
widgets::{ widgets::{
Axis, BarChart, Block, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List, ListItem, Axis, BarChart, Block, Borders, Cell, Chart, Clear, Dataset, Gauge, LineGauge, List,
Paragraph, Row, Sparkline, Table, Tabs, Wrap, ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap,
}, },
Frame, Frame,
}; };
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<()> { pub fn draw<B: Backend>(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<ListItem> = 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() let main_chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.margin(2) .margin(2)

@ -3,14 +3,14 @@ use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::{BufReader, Read}; use std::io::{BufReader, Read};
#[derive(Debug, Deserialize)] #[derive(Debug)]
pub struct UserConfig { pub struct UserConfig {
pub conn: Option<HashMap<String, Connection>>, pub connections: Vec<Connection>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Connection { pub struct Connection {
pub name: Option<String>, pub name: String,
pub user: String, pub user: String,
pub host: String, pub host: String,
pub port: u64, pub port: u64,
@ -18,14 +18,30 @@ pub struct Connection {
impl UserConfig { impl UserConfig {
pub fn new(path: &str) -> anyhow::Result<Self> { pub fn new(path: &str) -> anyhow::Result<Self> {
#[derive(Debug, Deserialize)]
pub struct ConfigFormat {
pub conn: HashMap<String, Connection>,
}
let file = File::open(path)?; let file = File::open(path)?;
let mut buf_reader = BufReader::new(file); let mut buf_reader = BufReader::new(file);
let mut contents = String::new(); let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?; buf_reader.read_to_string(&mut contents)?;
let config: Result<UserConfig, toml::de::Error> = toml::from_str(&contents); let config: Result<ConfigFormat, toml::de::Error> = toml::from_str(&contents);
match config { 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::<Vec<Connection>>(),
}),
Err(e) => panic!("fail to parse config file: {}", e), Err(e) => panic!("fail to parse config file: {}", e),
} }
} }

@ -19,7 +19,7 @@ pub async fn get_databases(pool: &MySqlPool) -> anyhow::Result<Vec<Database>> {
} }
pub async fn get_tables(database: String, pool: &MySqlPool) -> anyhow::Result<Vec<Table>> { pub async fn get_tables(database: String, pool: &MySqlPool) -> anyhow::Result<Vec<Table>> {
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) .fetch_all(pool)
.await? .await?
.iter() .iter()
@ -34,11 +34,11 @@ pub async fn get_records(
pool: &MySqlPool, pool: &MySqlPool,
) -> anyhow::Result<(Vec<String>, Vec<Vec<String>>)> { ) -> anyhow::Result<(Vec<String>, Vec<Vec<String>>)> {
&pool &pool
.execute(format!("use {}", database.name).as_str()) .execute(format!("use `{}`", database.name).as_str())
.await?; .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 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) .fetch_all(pool)
.await? .await?
.iter() .iter()
@ -93,14 +93,6 @@ pub async fn get_records(
let value: String = row.try_get(col_name).unwrap_or("".to_string()); let value: String = row.try_get(col_name).unwrap_or("".to_string());
row_vec.push(value); row_vec.push(value);
} }
// DATE
// VARCHAR
// INT UNSIGNED
// VARCHAR
// ENUM
// ENUM
// VARCHAR
// BOOLEAN
_ => (), _ => (),
} }
} }

Loading…
Cancel
Save