pub mod app;
pub mod input;
pub mod layout;
pub mod widgets;
use std::io;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use crossterm::event::{Event, EventStream, KeyEventKind};
use crossterm::execute;
use crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use futures::StreamExt;
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use tokio::sync::mpsc;
use crate::cosmos::CosmosBackend;
use crate::cosmos::meta;
use self::app::App;
use self::input::{InputOutcome, on_key};
pub struct TerminalGuard {
restored: bool,
}
impl TerminalGuard {
pub fn new() -> io::Result<Self> {
enable_raw_mode()?;
execute!(io::stdout(), EnterAlternateScreen)?;
Ok(Self { restored: false })
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
if !self.restored {
let _ = execute!(io::stdout(), LeaveAlternateScreen);
let _ = disable_raw_mode();
self.restored = true;
}
}
}
pub async fn run_status_dashboard(
cosmos: Arc<dyn CosmosBackend>,
meta_container: String,
refresh_interval: Duration,
) -> Result<()> {
let mut app = App::new();
let (poll_tx, mut poll_rx) = mpsc::channel::<Result<Vec<_>, String>>(4);
let cosmos_clone = cosmos.clone();
let container = meta_container.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(refresh_interval);
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
interval.tick().await;
let result = meta::list_all(cosmos_clone.as_ref(), &container)
.await
.map_err(|e| e.to_string());
if poll_tx.send(result).await.is_err() {
break; }
}
});
let _guard = TerminalGuard::new()?;
let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
let mut redraw_interval = tokio::time::interval(Duration::from_millis(100));
redraw_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
let mut key_events = EventStream::new();
loop {
tokio::select! {
_ = redraw_interval.tick() => {
while let Ok(result) = poll_rx.try_recv() {
app.handle_poll_result(result);
}
terminal.draw(|f| {
layout::draw(f, &app);
})?;
}
Some(Ok(ev)) = key_events.next() => {
if let Event::Key(key) = ev
&& key.kind == KeyEventKind::Press
{
if on_key(key, &mut app) == InputOutcome::Quit {
return Ok(());
}
terminal.draw(|f| layout::draw(f, &app))?;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn terminal_guard_constructs_and_drops_safely() {
let g = TerminalGuard { restored: false };
drop(g);
}
}