mod app;
mod config;
mod grafana;
mod prom;
mod theme;
mod ui;
use std::collections::HashMap;
use std::time::Duration;
use anyhow::{Context, Result};
use clap::Parser;
use config::Config;
use crossterm::{
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode},
};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use theme::Theme;
mod cli;
use cli::Args;
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
if let Some(cmd) = args.command {
match cmd {
cli::Commands::Completions { shell } => {
use clap::CommandFactory;
clap_complete::generate(
shell,
&mut Args::command(),
"grafatui",
&mut std::io::stdout(),
);
}
cli::Commands::Man => {
use clap::CommandFactory;
let man = clap_mangen::Man::new(Args::command());
man.render(&mut std::io::stdout())?;
}
}
return Ok(());
}
let config = match args.config.clone() {
Some(path) => Config::load(Some(path))?,
None => Config::load(None).unwrap_or_default(),
};
let prometheus_url = args
.prometheus_url
.or(config.prometheus_url)
.unwrap_or_else(|| "http://localhost:9090".to_string());
let range_str = args
.range
.or(config.time_range)
.unwrap_or_else(|| "5m".to_string());
let range = app::parse_duration(&range_str).context("--range")?;
let step_str = args
.step
.or(config.step)
.unwrap_or_else(|| "5s".to_string());
let step = app::parse_duration(&step_str).context("--step")?;
let refresh_rate = args.refresh_rate.or(config.refresh_rate).unwrap_or(1000);
let refresh_every = Duration::from_millis(refresh_rate);
let mut vars: HashMap<String, String> = HashMap::new();
let prom = prom::PromClient::new(prometheus_url);
let (title, panels, skipped_panels) = if let Some(path) = args
.grafana_json
.or(config.grafana_json)
.map(|p| config::expand_path(&p))
{
let d = grafana::load_grafana_dashboard(&path)?;
for (k, v) in d.vars {
vars.insert(k, v);
}
let ps = d
.queries
.into_iter()
.map(|q| app::PanelState {
title: q.title,
exprs: q.exprs,
legends: q.legends,
series: vec![],
last_error: None,
last_url: None,
last_samples: 0,
grid: q.grid.map(|g| app::GridUnit {
x: g.x,
y: g.y,
w: g.w,
h: g.h,
}),
y_axis_mode: app::YAxisMode::Auto,
panel_type: q.panel_type,
thresholds: q.thresholds,
min: q.min,
max: q.max,
})
.collect();
(format!("{} (imported)", d.title), ps, d.skipped_panels)
} else {
("grafatui".to_string(), app::default_queries(args.query), 0)
};
if let Some(config_vars) = config.vars {
for (k, v) in config_vars {
vars.insert(k, v);
}
}
for (k, v) in &args.var {
vars.insert(k.clone(), v.clone());
}
let theme_name = args
.theme
.or(config.theme)
.unwrap_or_else(|| "default".to_string());
let theme = Theme::from_str(&theme_name);
let marker_name = args
.threshold_marker
.or(config.threshold_marker)
.unwrap_or_else(|| "dashed-line".to_string());
let mut state = app::AppState::new(
prom,
range,
step,
refresh_every,
title,
panels,
skipped_panels,
theme,
marker_name,
);
state.vars = vars; state.refresh().await?;
crossterm::terminal::enable_raw_mode()?;
let mut stdout = std::io::stdout();
execute!(
stdout,
EnterAlternateScreen,
crossterm::event::EnableMouseCapture
)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let res = app::run_app(
&mut terminal,
&mut state,
Duration::from_millis(args.tick_rate),
)
.await;
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
crossterm::event::DisableMouseCapture
)?;
terminal.show_cursor()?;
res
}