diff --git a/src/app.rs b/src/app.rs index 42be961..af6f970 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,94 +1,119 @@ -use ratatui::backend::CrosstermBackend; use ratatui::layout::Rect; +use ratatui::prelude::*; use ratatui::style::{Color, Style}; -use ratatui::symbols::line; -use ratatui::widgets::{Block, Borders, Clear, LineGauge, Paragraph}; -use ratatui::{Terminal, TerminalOptions, Viewport}; +use ratatui::widgets::{Block, Borders, Gauge}; -use sysinfo::{Components, Disks, Networks, System}; +use sysinfo::{Components, Disk, Disks, Networks, System}; -#[derive(Debug, PartialEq)] -pub enum Action { - Continue, - Stop, +pub struct App { + sys: System, } -pub struct App { - pub terminal: Terminal>, - pub counter: usize, +impl Default for App { + fn default() -> Self { + App { + sys: System::new_all(), + } + } } -impl App { - pub fn new(terminal_handle: W) -> Result { - let backend = CrosstermBackend::new(terminal_handle); - - // the correct viewport area will be set when the client request a pty - let options = TerminalOptions { - viewport: Viewport::Fixed(Rect::default()), - }; - - let terminal = Terminal::with_options(backend, options)?; - - Ok(Self { - terminal, - counter: 0, - }) +impl App { + pub fn update_info(&mut self) { + self.sys.refresh_all(); } - pub fn input(&mut self, data: &[u8]) -> Action { - match data { - // Pressing 'q' closes the connection. - b"q" => Action::Stop, - // Pressing 'c' resets the counter for the app. - // Only the client with the id sees the counter reset. - b"c" => { - self.counter = 0; - Action::Continue + pub fn draw(&self, frame: &mut Frame) { + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(frame.area()); + + let style = Style::default().fg(Color::Red); + let block = Block::default().title("Services").borders(Borders::ALL); + + frame.render_widget(block, layout[0]); + + let disks_gauges = self.disks(); + + let layout_system = Layout::vertical([ + Constraint::Length(6), + Constraint::Length((disks_gauges.len() * 2 + 2) as u16), + ]) + .split(layout[1]); + + self.draw_memory(frame, layout_system[0]); + self.draw_disks(frame, layout_system[1], disks_gauges); + } + + fn disks(&self) -> Vec { + let mut gauges: Vec = Default::default(); + + for disk in &Disks::new_with_refreshed_list() { + if disk.mount_point().starts_with("/boot/") || disk.mount_point().starts_with("/tmp/") { + continue; } - _ => Action::Continue, + + let total = ((disk.total_space() >> 20) as f64) / 1024.0; + let used = total - ((disk.available_space() >> 20) as f64) / 1024.0; + let gauge = Gauge::default() + .block(Block::default().title(format!( + "{:?} ({:?})", + disk.mount_point(), + disk.kind() + ))) + .gauge_style(Style::new().white().on_black().italic()) + .label(format!("{used:.2}/{total:.2}Go")) + .ratio(used / total); + + gauges.push(gauge); + } + + gauges + } + + fn draw_disks(&self, frame: &mut Frame, area: Rect, gauges: Vec) { + let block = Block::default().title("Disks").borders(Borders::ALL); + + frame.render_widget(block, area); + + let layout = Layout::vertical(vec![Constraint::Length(2); gauges.len()]) + .margin(1) + .split(area); + + for (idx, gauge) in (&gauges).into_iter().enumerate() { + frame.render_widget(gauge, layout[idx]); } } - pub fn update(&mut self) { - if (self.counter % 100) == 0 { - let mut sys = System::new_all(); - sys.refresh_all(); + fn draw_memory(&self, frame: &mut Frame, area: Rect) { + let block = Block::default().title("Memory").borders(Borders::ALL); - self.terminal - .draw(|f| { - let area = f.area(); - f.render_widget(Clear, area); - let style = Style::default().fg(Color::Red); - let block = Block::default() - .title("Press 'q' to quit") - .borders(Borders::ALL); + frame.render_widget(block, area); - let mem_total: f64 = ((sys.total_memory() >> 20) as f64) / 1024.0; - let mem_used: f64 = ((sys.used_memory() >> 20) as f64) / 1024.0; + let layout = Layout::vertical([Constraint::Length(2), Constraint::Length(2)]) + .margin(1) + .split(area); - let line_gauge = LineGauge::default() - .block(Block::bordered().title("RAM usage")) - .label(format!("{mem_used:.2}/{mem_total:.2}Go")) - .ratio(mem_used / mem_total) - .filled_symbol("=") - .unfilled_symbol(" "); - f.render_widget(line_gauge.block(block), area); - }) - .unwrap(); - } + let mem_total: f64 = ((self.sys.total_memory() >> 20) as f64) / 1024.0; + let mem_used: f64 = ((self.sys.used_memory() >> 20) as f64) / 1024.0; - self.counter += 1; - } + let mem_gauge = Gauge::default() + .block(Block::default().title("RAM usage:")) + .gauge_style(Style::new().white().on_black().italic()) + .label(format!("{mem_used:.2}/{mem_total:.2}Go")) + .ratio(mem_used / mem_total); - pub fn resize(&mut self, col: u32, row: u32) -> Result<(), anyhow::Error> { - let rect = Rect { - x: 0, - y: 0, - width: col as u16, - height: row as u16, - }; + frame.render_widget(mem_gauge, layout[0]); - self.terminal.resize(rect)?; - Ok(()) + let swap_total: f64 = ((self.sys.total_swap() >> 20) as f64) / 1024.0; + let swap_used: f64 = ((self.sys.used_swap() >> 20) as f64) / 1024.0; + + let swap_gauge = Gauge::default() + .block(Block::default().title("SWAP usage:")) + .gauge_style(Style::new().white().on_black().italic()) + .label(format!("{swap_used:.2}/{swap_total:.2}Go")) + .ratio(swap_used / swap_total); + + frame.render_widget(swap_gauge, layout[1]); } } diff --git a/src/bin/tui.rs b/src/bin/tui.rs index d53a884..40c81c3 100644 --- a/src/bin/tui.rs +++ b/src/bin/tui.rs @@ -1,5 +1,5 @@ //extern crate termios; -use ssh_status::app::{Action, App}; +use ssh_status::runner::{Action, Runner}; use std::io::{self, Write}; use std::time::Duration; @@ -18,13 +18,13 @@ fn main() { ) .unwrap(); - let mut app = App::new(stdout).unwrap(); + let mut app = Runner::new(stdout).unwrap(); let (w, h) = crossterm::terminal::size().unwrap(); app.resize(w as u32, h as u32).unwrap(); app.update(); loop { - if crossterm::event::poll(Duration::from_millis(10)).unwrap() { + if crossterm::event::poll(Duration::from_millis(100)).unwrap() { match crossterm::event::read().unwrap() { Event::Key(key) => match key.code { KeyCode::Char(c) => { diff --git a/src/lib.rs b/src/lib.rs index d764a0e..04490e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod app; +pub mod runner; pub mod server; diff --git a/src/runner.rs b/src/runner.rs new file mode 100644 index 0000000..8270eeb --- /dev/null +++ b/src/runner.rs @@ -0,0 +1,58 @@ +use ratatui::backend::CrosstermBackend; +use ratatui::layout::Rect; +use ratatui::{Terminal, TerminalOptions, Viewport}; + +use crate::app::App; + +#[derive(Debug, PartialEq)] +pub enum Action { + Continue, + Stop, +} + +pub struct Runner { + pub terminal: Terminal>, + pub app: App, +} + +impl Runner { + pub fn new(terminal_handle: W) -> Result { + let backend = CrosstermBackend::new(terminal_handle); + + // the correct viewport area will be set when the client request a pty + let options = TerminalOptions { + viewport: Viewport::Fixed(Rect::default()), + }; + + let terminal = Terminal::with_options(backend, options)?; + + Ok(Self { + terminal, + app: App::default(), + }) + } + + pub fn input(&mut self, data: &[u8]) -> Action { + match data { + b"q" => Action::Stop, + _ => Action::Continue, + } + } + + pub fn update(&mut self) { + self.app.update_info(); + self.terminal.draw(|frame| self.app.draw(frame)).unwrap(); + } + + pub fn resize(&mut self, col: u32, row: u32) -> Result<(), anyhow::Error> { + let rect = Rect { + x: 0, + y: 0, + width: col as u16, + height: row as u16, + }; + + self.terminal.resize(rect)?; + Ok(()) + } +} diff --git a/src/server.rs b/src/server.rs index 4736893..c7030bd 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,11 +7,11 @@ use russh::{Channel, ChannelId, Pty}; use tokio::sync::Mutex; use tokio::sync::mpsc::{UnboundedSender, unbounded_channel}; -use crate::app::{Action, App}; +use crate::runner::{Action, Runner}; #[derive(Clone)] pub struct AppServer { - clients: Arc>>>, + clients: Arc>>>, id: usize, } @@ -27,7 +27,7 @@ impl AppServer { let clients = self.clients.clone(); tokio::spawn(async move { loop { - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; for (_, app) in clients.lock().await.iter_mut() { app.update(); @@ -70,7 +70,7 @@ impl Handler for AppServer { session: &mut Session, ) -> Result { let terminal_handle = TerminalHandle::start(session.handle(), channel.id()).await; - let app = App::new(terminal_handle)?; + let app = Runner::new(terminal_handle)?; let mut clients = self.clients.lock().await; clients.insert(self.id, app);