add disk usage
This commit is contained in:
173
src/app.rs
173
src/app.rs
@@ -1,94 +1,119 @@
|
|||||||
use ratatui::backend::CrosstermBackend;
|
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
|
use ratatui::prelude::*;
|
||||||
use ratatui::style::{Color, Style};
|
use ratatui::style::{Color, Style};
|
||||||
use ratatui::symbols::line;
|
use ratatui::widgets::{Block, Borders, Gauge};
|
||||||
use ratatui::widgets::{Block, Borders, Clear, LineGauge, Paragraph};
|
|
||||||
use ratatui::{Terminal, TerminalOptions, Viewport};
|
|
||||||
|
|
||||||
use sysinfo::{Components, Disks, Networks, System};
|
use sysinfo::{Components, Disk, Disks, Networks, System};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
pub struct App {
|
||||||
pub enum Action {
|
sys: System,
|
||||||
Continue,
|
|
||||||
Stop,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App<W: std::io::Write> {
|
impl Default for App {
|
||||||
pub terminal: Terminal<CrosstermBackend<W>>,
|
fn default() -> Self {
|
||||||
pub counter: usize,
|
App {
|
||||||
|
sys: System::new_all(),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: std::io::Write> App<W> {
|
|
||||||
pub fn new(terminal_handle: W) -> Result<Self, anyhow::Error> {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
_ => Action::Continue,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
impl App {
|
||||||
if (self.counter % 100) == 0 {
|
pub fn update_info(&mut self) {
|
||||||
let mut sys = System::new_all();
|
self.sys.refresh_all();
|
||||||
sys.refresh_all();
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
self.terminal
|
|
||||||
.draw(|f| {
|
|
||||||
let area = f.area();
|
|
||||||
f.render_widget(Clear, area);
|
|
||||||
let style = Style::default().fg(Color::Red);
|
let style = Style::default().fg(Color::Red);
|
||||||
let block = Block::default()
|
let block = Block::default().title("Services").borders(Borders::ALL);
|
||||||
.title("Press 'q' to quit")
|
|
||||||
.borders(Borders::ALL);
|
|
||||||
|
|
||||||
let mem_total: f64 = ((sys.total_memory() >> 20) as f64) / 1024.0;
|
frame.render_widget(block, layout[0]);
|
||||||
let mem_used: f64 = ((sys.used_memory() >> 20) as f64) / 1024.0;
|
|
||||||
|
|
||||||
let line_gauge = LineGauge::default()
|
let disks_gauges = self.disks();
|
||||||
.block(Block::bordered().title("RAM usage"))
|
|
||||||
|
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<Gauge> {
|
||||||
|
let mut gauges: Vec<Gauge> = Default::default();
|
||||||
|
|
||||||
|
for disk in &Disks::new_with_refreshed_list() {
|
||||||
|
if disk.mount_point().starts_with("/boot/") || disk.mount_point().starts_with("/tmp/") {
|
||||||
|
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<Gauge>) {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_memory(&self, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::default().title("Memory").borders(Borders::ALL);
|
||||||
|
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
|
||||||
|
let layout = Layout::vertical([Constraint::Length(2), Constraint::Length(2)])
|
||||||
|
.margin(1)
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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"))
|
.label(format!("{mem_used:.2}/{mem_total:.2}Go"))
|
||||||
.ratio(mem_used / mem_total)
|
.ratio(mem_used / mem_total);
|
||||||
.filled_symbol("=")
|
|
||||||
.unfilled_symbol(" ");
|
|
||||||
f.render_widget(line_gauge.block(block), area);
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.counter += 1;
|
frame.render_widget(mem_gauge, layout[0]);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, col: u32, row: u32) -> Result<(), anyhow::Error> {
|
let swap_total: f64 = ((self.sys.total_swap() >> 20) as f64) / 1024.0;
|
||||||
let rect = Rect {
|
let swap_used: f64 = ((self.sys.used_swap() >> 20) as f64) / 1024.0;
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: col as u16,
|
|
||||||
height: row as u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.terminal.resize(rect)?;
|
let swap_gauge = Gauge::default()
|
||||||
Ok(())
|
.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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//extern crate termios;
|
//extern crate termios;
|
||||||
use ssh_status::app::{Action, App};
|
use ssh_status::runner::{Action, Runner};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -18,13 +18,13 @@ fn main() {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut app = App::new(stdout).unwrap();
|
let mut app = Runner::new(stdout).unwrap();
|
||||||
let (w, h) = crossterm::terminal::size().unwrap();
|
let (w, h) = crossterm::terminal::size().unwrap();
|
||||||
app.resize(w as u32, h as u32).unwrap();
|
app.resize(w as u32, h as u32).unwrap();
|
||||||
app.update();
|
app.update();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if crossterm::event::poll(Duration::from_millis(10)).unwrap() {
|
if crossterm::event::poll(Duration::from_millis(100)).unwrap() {
|
||||||
match crossterm::event::read().unwrap() {
|
match crossterm::event::read().unwrap() {
|
||||||
Event::Key(key) => match key.code {
|
Event::Key(key) => match key.code {
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
|
pub mod runner;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|||||||
58
src/runner.rs
Normal file
58
src/runner.rs
Normal file
@@ -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<W: std::io::Write> {
|
||||||
|
pub terminal: Terminal<CrosstermBackend<W>>,
|
||||||
|
pub app: App,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: std::io::Write> Runner<W> {
|
||||||
|
pub fn new(terminal_handle: W) -> Result<Self, anyhow::Error> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,11 @@ use russh::{Channel, ChannelId, Pty};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
|
use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
|
||||||
|
|
||||||
use crate::app::{Action, App};
|
use crate::runner::{Action, Runner};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppServer {
|
pub struct AppServer {
|
||||||
clients: Arc<Mutex<HashMap<usize, App<TerminalHandle>>>>,
|
clients: Arc<Mutex<HashMap<usize, Runner<TerminalHandle>>>>,
|
||||||
id: usize,
|
id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ impl AppServer {
|
|||||||
let clients = self.clients.clone();
|
let clients = self.clients.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
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() {
|
for (_, app) in clients.lock().await.iter_mut() {
|
||||||
app.update();
|
app.update();
|
||||||
@@ -70,7 +70,7 @@ impl Handler for AppServer {
|
|||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
) -> Result<bool, Self::Error> {
|
) -> Result<bool, Self::Error> {
|
||||||
let terminal_handle = TerminalHandle::start(session.handle(), channel.id()).await;
|
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;
|
let mut clients = self.clients.lock().await;
|
||||||
clients.insert(self.id, app);
|
clients.insert(self.id, app);
|
||||||
|
|||||||
Reference in New Issue
Block a user