137 lines
5.3 KiB
Rust
137 lines
5.3 KiB
Rust
use std::time::{Duration, Instant};
|
|
|
|
use crossterm::{
|
|
event::{self, Event, KeyCode},
|
|
execute,
|
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
};
|
|
use ratatui::{
|
|
backend::CrosstermBackend,
|
|
Terminal,
|
|
layout::{Constraint, Direction, Layout},
|
|
style::{Color, Style},
|
|
text::{Line, Span},
|
|
widgets::{Block, Borders, List, ListItem, Paragraph},
|
|
};
|
|
|
|
use crate::state::{SharedState, ChromiumPhase, HeadlessPhase};
|
|
|
|
pub fn start_ui(state: SharedState) -> std::io::Result<()> {
|
|
enable_raw_mode()?;
|
|
let mut stdout = std::io::stdout();
|
|
execute!(stdout, EnterAlternateScreen)?;
|
|
let backend = CrosstermBackend::new(stdout);
|
|
let mut terminal = Terminal::new(backend)?;
|
|
|
|
let mut last_tick = Instant::now();
|
|
let tick_rate = Duration::from_millis(250);
|
|
|
|
loop {
|
|
let snapshot = {
|
|
let s = state.lock().unwrap();
|
|
s.clone()
|
|
};
|
|
|
|
terminal.draw(|f| {
|
|
let chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.margin(1)
|
|
.constraints([
|
|
Constraint::Length(4),
|
|
Constraint::Min(6),
|
|
Constraint::Min(6),
|
|
])
|
|
.split(f.size());
|
|
|
|
let header = Block::default()
|
|
.title(" Aurora Core — Engines ")
|
|
.borders(Borders::ALL);
|
|
|
|
let web_dot = Span::styled(
|
|
"●",
|
|
Style::default().fg(if snapshot.web_server_active { Color::Green } else { Color::Red })
|
|
);
|
|
let plug_dot = Span::styled(
|
|
"●",
|
|
Style::default().fg(if snapshot.plugin_engine_active { Color::Green } else { Color::Red })
|
|
);
|
|
|
|
// Chromium Punkt + optionaler Text
|
|
let (chrom_dot_color, chrom_text) = match &snapshot.chromium {
|
|
ChromiumPhase::Ready => (Color::Green, "".to_string()),
|
|
ChromiumPhase::Downloading => (Color::Yellow, "downloading…".to_string()),
|
|
ChromiumPhase::Checking => (Color::Yellow, "checking…".to_string()),
|
|
ChromiumPhase::Idle => (Color::Red, "".to_string()),
|
|
ChromiumPhase::Error(msg) => (Color::Red, format!("error: {}", msg)),
|
|
};
|
|
let chrom_dot = Span::styled("●", Style::default().fg(chrom_dot_color));
|
|
|
|
// Headless Engine Punkt + optionaler Text
|
|
let (head_dot_color, head_text) = match &snapshot.headless {
|
|
HeadlessPhase::Ready => (Color::Green, "".to_string()),
|
|
HeadlessPhase::Starting => (Color::Yellow, "starting…".to_string()),
|
|
HeadlessPhase::Idle => (Color::Red, "".to_string()),
|
|
HeadlessPhase::Error(msg) => (Color::Red, format!("error: {}", msg)),
|
|
};
|
|
let head_dot = Span::styled("●", Style::default().fg(head_dot_color));
|
|
|
|
let mut spans = vec![
|
|
Span::raw("Web Server "), web_dot, Span::raw(" "),
|
|
Span::raw("Plugin Engine "), plug_dot, Span::raw(" "),
|
|
Span::raw("Chromium "), chrom_dot,
|
|
];
|
|
if !chrom_text.is_empty() {
|
|
spans.push(Span::raw(" "));
|
|
spans.push(Span::styled(format!("({})", chrom_text), Style::default().fg(Color::Gray)));
|
|
}
|
|
spans.push(Span::raw(" "));
|
|
|
|
spans.push(Span::raw("Headless Engine "));
|
|
spans.push(head_dot);
|
|
if !head_text.is_empty() {
|
|
spans.push(Span::raw(" "));
|
|
spans.push(Span::styled(format!("({})", head_text), Style::default().fg(Color::Gray)));
|
|
}
|
|
|
|
let header_para = Paragraph::new(Line::from(spans)).block(header);
|
|
f.render_widget(header_para, chunks[0]);
|
|
|
|
// Loaded plugins
|
|
let plugin_block = Block::default().title(" Loaded Plugins ").borders(Borders::ALL);
|
|
let items: Vec<ListItem> = if snapshot.loaded_plugins.is_empty() {
|
|
vec![ListItem::new("— none —")]
|
|
} else {
|
|
snapshot.loaded_plugins.iter().map(|p| ListItem::new(p.clone())).collect()
|
|
};
|
|
f.render_widget(List::new(items).block(plugin_block), chunks[1]);
|
|
|
|
// Running jobs
|
|
let jobs_block = Block::default().title(" Active Setups ").borders(Borders::ALL);
|
|
let jobs_items: Vec<ListItem> = if snapshot.running_jobs.is_empty() {
|
|
vec![ListItem::new("— idle —")]
|
|
} else {
|
|
snapshot.running_jobs.iter().map(|j| ListItem::new(j.clone())).collect()
|
|
};
|
|
f.render_widget(List::new(jobs_items).block(jobs_block), chunks[2]);
|
|
})?;
|
|
|
|
let timeout = tick_rate.checked_sub(last_tick.elapsed()).unwrap_or(Duration::from_secs(0));
|
|
|
|
if crossterm::event::poll(timeout)? {
|
|
if let Event::Key(key) = event::read()? {
|
|
match key.code {
|
|
KeyCode::Char('q') | KeyCode::Esc => break,
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
if last_tick.elapsed() >= tick_rate {
|
|
last_tick = Instant::now();
|
|
}
|
|
}
|
|
|
|
disable_raw_mode()?;
|
|
let mut stdout = std::io::stdout();
|
|
execute!(stdout, LeaveAlternateScreen)?;
|
|
Ok(())
|
|
}
|