Skip to main content

max / mountaineer

3.0 KB · 107 lines History Blame Raw
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