| 1 |
use crossterm::event::{self, Event, KeyCode}; |
| 2 |
use ratatui::style::Style; |
| 3 |
use ratatui::text::{Line, Span}; |
| 4 |
use ratatui::widgets::Paragraph; |
| 5 |
use std::io; |
| 6 |
use sysop_tui::{Footer, GlobalAction, classify, hint, layout, palette, selection, style}; |
| 7 |
|
| 8 |
enum State { |
| 9 |
Ok, |
| 10 |
Warn, |
| 11 |
Fault, |
| 12 |
Inactive, |
| 13 |
} |
| 14 |
|
| 15 |
struct Service { |
| 16 |
name: &'static str, |
| 17 |
state: State, |
| 18 |
} |
| 19 |
|
| 20 |
fn state_span(s: &State) -> Span<'static> { |
| 21 |
match s { |
| 22 |
State::Ok => style::ok("ok"), |
| 23 |
State::Warn => style::warn("syncing"), |
| 24 |
State::Fault => style::fault("down"), |
| 25 |
State::Inactive => style::inactive("stopped"), |
| 26 |
} |
| 27 |
} |
| 28 |
|
| 29 |
fn services() -> Vec<Service> { |
| 30 |
vec![ |
| 31 |
Service { name: "sshd", state: State::Ok }, |
| 32 |
Service { name: "unbound", state: State::Ok }, |
| 33 |
Service { name: "chronyd", state: State::Ok }, |
| 34 |
Service { name: "caddy", state: State::Warn }, |
| 35 |
Service { name: "nebula", state: State::Fault }, |
| 36 |
Service { name: "tlp", state: State::Inactive }, |
| 37 |
] |
| 38 |
} |
| 39 |
|
| 40 |
fn body(items: &[Service], selected: usize) -> Paragraph<'_> { |
| 41 |
let lines: Vec<Line> = items |
| 42 |
.iter() |
| 43 |
.enumerate() |
| 44 |
.map(|(i, s)| { |
| 45 |
let is_sel = i == selected; |
| 46 |
let marker = if is_sel { selection::MARKER } else { " " }; |
| 47 |
let line = Line::from(vec![ |
| 48 |
Span::raw(format!(" {} ", marker)), |
| 49 |
Span::raw(format!("{:<12}", s.name)), |
| 50 |
Span::raw(" "), |
| 51 |
state_span(&s.state), |
| 52 |
]); |
| 53 |
if is_sel { |
| 54 |
line.style(selection::selected_style()) |
| 55 |
} else { |
| 56 |
line |
| 57 |
} |
| 58 |
}) |
| 59 |
.collect(); |
| 60 |
Paragraph::new(lines).style(Style::default().bg(palette::BG).fg(palette::FG)) |
| 61 |
} |
| 62 |
|
| 63 |
fn main() -> io::Result<()> { |
| 64 |
let mut terminal = ratatui::init(); |
| 65 |
let services = services(); |
| 66 |
let mut idx: usize = 0; |
| 67 |
|
| 68 |
let footer = Footer::new([ |
| 69 |
hint("j/k", "move"), |
| 70 |
hint("Enter", "details"), |
| 71 |
hint("?", "help"), |
| 72 |
hint("q", "quit"), |
| 73 |
]); |
| 74 |
|
| 75 |
let result: io::Result<()> = loop { |
| 76 |
let draw = terminal.draw(|frame| { |
| 77 |
let [body_area, foot_area] = layout::split(frame.area()); |
| 78 |
frame.render_widget(body(&services, idx), body_area); |
| 79 |
frame.render_widget(&footer, foot_area); |
| 80 |
}); |
| 81 |
if let Err(e) = draw { |
| 82 |
break Err(e); |
| 83 |
} |
| 84 |
|
| 85 |
match event::read() { |
| 86 |
Ok(Event::Key(key)) => match classify(key) { |
| 87 |
GlobalAction::Quit => break Ok(()), |
| 88 |
GlobalAction::Passthrough => match key.code { |
| 89 |
KeyCode::Char('j') | KeyCode::Down if idx + 1 < services.len() => { |
| 90 |
idx += 1; |
| 91 |
} |
| 92 |
KeyCode::Char('k') | KeyCode::Up => { |
| 93 |
idx = idx.saturating_sub(1); |
| 94 |
} |
| 95 |
_ => {} |
| 96 |
}, |
| 97 |
_ => {} |
| 98 |
}, |
| 99 |
Ok(_) => {} |
| 100 |
Err(e) => break Err(e), |
| 101 |
} |
| 102 |
}; |
| 103 |
|
| 104 |
ratatui::restore(); |
| 105 |
result |
| 106 |
} |
| 107 |
|