use std::ops::ControlFlow;
use std::time::Duration;
use ratatui::layout::Constraint;
use ratatui::widgets::{Block, Borders, Row, Sparkline, Table, Widget};
use tuika::{
AsyncRunner, Dimension, Element, Event, Flex, KeyCode, KeyHints, RatatuiView, RunnerConfig,
Signal, Theme, element,
};
struct Metrics {
requests: u64,
errors: u64,
history: Vec<u64>,
}
impl Metrics {
fn new() -> Self {
Self {
requests: 0,
errors: 0,
history: Vec::new(),
}
}
fn ingest(&mut self, requests: u64) {
self.requests = requests;
self.errors = requests / 7;
self.history.push(requests % 20);
if self.history.len() > 40 {
self.history.remove(0);
}
}
}
async fn fetch_stats(previous: u64) -> u64 {
tokio::time::sleep(Duration::from_millis(20)).await;
previous + 1
}
fn table(metrics: &Metrics) -> Element {
let (requests, errors) = (metrics.requests, metrics.errors);
element(RatatuiView::fill(move |area, buffer| {
Table::new(
[
Row::new(["requests", &requests.to_string()]),
Row::new(["errors", &errors.to_string()]),
],
[Constraint::Percentage(60), Constraint::Percentage(40)],
)
.block(Block::bordered().title(" Metrics "))
.render(area, buffer);
}))
}
fn sparkline(metrics: &Metrics) -> Element {
let history = metrics.history.clone();
element(RatatuiView::fill(move |area, buffer| {
Sparkline::default()
.data(&history)
.block(Block::default().borders(Borders::ALL).title(" Requests "))
.render(area, buffer);
}))
}
fn dashboard(metrics: &Metrics) -> Element {
element(
Flex::column()
.gap(1)
.child(Dimension::Fixed(4), table(metrics))
.child(Dimension::Flex(1), sparkline(metrics))
.child(
Dimension::Fixed(1),
element(KeyHints::new([("q", "quit"), ("r", "refresh")])),
),
)
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> std::io::Result<()> {
let runner = AsyncRunner::new(RunnerConfig {
tick_rate: Duration::from_millis(500),
});
let mut metrics = Metrics::new();
runner
.run(
&Theme::default(),
&mut metrics,
|metrics, _frame| dashboard(metrics),
async |metrics, signal| match signal {
Signal::Tick => {
metrics.ingest(fetch_stats(metrics.requests).await);
ControlFlow::Continue(())
}
Signal::Event(Event::Key(key)) if key.plain() => match key.code {
KeyCode::Char('q') | KeyCode::Esc => ControlFlow::Break(()),
KeyCode::Char('r') => {
metrics.ingest(fetch_stats(metrics.requests).await);
ControlFlow::Continue(())
}
_ => ControlFlow::Continue(()),
},
_ => ControlFlow::Continue(()),
},
)
.await
}