diff options
author | Ashelyn Rose <git@ashen.earth> | 2024-11-07 20:07:25 -0700 |
---|---|---|
committer | Ashelyn Rose <git@ashen.earth> | 2024-11-07 20:07:25 -0700 |
commit | a69d779cfff810edd375318c3d36fecd42d294b5 (patch) | |
tree | d51ce593e45a800bf63c5b41caf82df151f03245 /src/main.rs | |
parent | ba9362908d2defa784a45c16bc142c66c3fab9a3 (diff) |
Runtime ui
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 195 |
1 files changed, 179 insertions, 16 deletions
diff --git a/src/main.rs b/src/main.rs index 6e37938..287cd4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,41 +2,141 @@ mod config; mod system; +use crossterm::{cursor::{self, MoveTo}, terminal::{Clear, ClearType, DisableLineWrap, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen}}; use system::{Manager, SystemThreadCommand}; -use std::{fs, thread::{self, sleep, JoinHandle}, time::Duration, sync::mpsc}; +use std::{collections::{HashMap, VecDeque}, fs, io::{self, Write}, sync::mpsc, thread::{self, sleep, JoinHandle}, time::Duration}; use tokio::runtime; +pub struct UiState { + pub systems: HashMap<String, SystemState>, + pub logs: VecDeque<String>, +} + +pub enum SystemState { + Running(HashMap<String, MemberState>), + Reloading, + Restarting, + Shutdown, +} + +pub struct MemberState { + pub connected: bool, + pub autoproxied: bool, +} + +pub enum SystemUiEvent { + SystemClose, + MemberAutoproxy(Option<String>), + GatewayDisconnect(String), + GatewayConnect(String), + LogLine(String), +} + +const MAX_LOG : usize = 1000; + fn main() { let initial_config = fs::read_to_string("./config.toml").expect("Could not find config file"); let config = config::Config::load(initial_config.to_string()); - let (waker, waiter) = mpsc::channel::<()>(); + let (waker, waiter) = mpsc::channel::<(String, SystemUiEvent)>(); let mut join_handles = Vec::<(String, JoinHandle<_>)>::new(); + let mut ui_state = UiState { + systems: HashMap::new(), + logs: VecDeque::new(), + }; + for (system_name, system_config) in config.systems.iter() { let handle = spawn_system(system_name, system_config.clone(), waker.clone()); + let mut member_states = HashMap::new(); + for member_name in system_config.members.iter() { + member_states.insert(member_name.name.clone(), MemberState { + connected: false, + autoproxied: false, + }); + } + + let system_state = SystemState::Running(member_states); + ui_state.systems.insert(system_name.clone(), system_state); + join_handles.push((system_name.clone(), handle)); } + crossterm::execute!(io::stdout(), EnterAlternateScreen).unwrap(); + crossterm::execute!(io::stdout(), DisableLineWrap).unwrap(); + loop { - // Check manually every 10 seconds just in case - let _ = waiter.recv_timeout(Duration::from_secs(10)); + // Wait for an event from one of the threads + let ui_event = waiter.recv_timeout(Duration::from_millis(500)); + + if let Ok((system_name, ui_event)) = ui_event { + let system_state = ui_state.systems.get_mut(&system_name).unwrap(); + match system_state { + SystemState::Running(member_states) => match ui_event { + // We will check for the join in a second + SystemUiEvent::SystemClose => (), + + SystemUiEvent::MemberAutoproxy(member_name) => { + member_states.iter_mut().for_each(|(_, member_state)| { + member_state.autoproxied = false; + }); + + if let Some(member_name) = member_name { + member_states.get_mut(&member_name).unwrap() + .autoproxied = true; + } + }, + + SystemUiEvent::GatewayDisconnect(member_name) => { + member_states.get_mut(&member_name).unwrap() + .connected = false; + }, + + SystemUiEvent::GatewayConnect(member_name) => { + member_states.get_mut(&member_name).unwrap() + .connected = true; + + }, + + SystemUiEvent::LogLine(log) => { + if log.len() == MAX_LOG { + let _ = ui_state.logs.pop_front(); + } + + ui_state.logs.push_back( + format!("{system_name:>8.8}: {log}") + ); + }, + + }, + _ => (), + } + } // Just to make sure the join handle is updated by the time we check - sleep(Duration::from_millis(100)); + sleep(Duration::from_millis(10)); if let Some(completed_index) = join_handles.iter().position(|(_, handle)| handle.is_finished()) { let (name, next_join) = join_handles.swap_remove(completed_index); match next_join.join() { Err(err) => { - println!("Thread for system {} panicked!", name); - println!("{:?}", err); + let _ = ui_state.systems.insert(name.clone(), SystemState::Shutdown); + ui_state.logs.push_back( + format!("Thread for system {} panicked!", name) + ); + + ui_state.logs.push_back( + format!("{:?}", err) + ); }, Ok(SystemThreadCommand::Restart) => { - println!("Thread for system {} requested restart", name); + let _ = ui_state.systems.insert(name.clone(), SystemState::Restarting); + ui_state.logs.push_back( + format!("Thread for system {} requested restart", name) + ); if let Some((_, config)) = config.systems.iter().find(|(system_name, _)| name == **system_name) { let handle = spawn_system(&name, config.clone(), waker.clone()); join_handles.push((name, handle)); @@ -44,15 +144,24 @@ fn main() { }, Ok(SystemThreadCommand::ShutdownSystem) => { - println!("Thread for system {} requested shutdown", name); + let _ = ui_state.systems.insert(name.clone(), SystemState::Shutdown); + ui_state.logs.push_back( + format!("Thread for system {} requested shutdown", name) + ); continue; }, + Ok(SystemThreadCommand::ReloadConfig) => { - println!("Thread for system {} requested config reload", name); + let _ = ui_state.systems.insert(name.clone(), SystemState::Reloading); + ui_state.logs.push_back( + format!("Thread for system {} requested config reload", name) + ); let config_file = if let Ok(config_file) = fs::read_to_string("./config.toml") { config_file } else { - println!("Could not open config file, continuing with initial config"); + ui_state.logs.push_back( + format!("Could not open config file, continuing with initial config") + ); initial_config.clone() }; @@ -62,17 +171,24 @@ fn main() { let handle = spawn_system(&name, system_config, waker.clone()); join_handles.push((name.clone(), handle)); } else { - println!("New config file but this system no longer exists, exiting."); + ui_state.logs.push_back( + format!("New config file but this system no longer exists, exiting.") + ); continue; } }, Ok(SystemThreadCommand::ShutdownAll) => break, } } + + update_ui(&ui_state, &config); } + + crossterm::execute!(io::stdout(), EnableLineWrap).unwrap(); + crossterm::execute!(io::stdout(), LeaveAlternateScreen).unwrap(); } -fn spawn_system(system_name : &String, system_config: config::System, waker: mpsc::Sender<()>) -> JoinHandle<SystemThreadCommand> { +fn spawn_system(system_name : &String, system_config: config::System, waker: mpsc::Sender<(String, SystemUiEvent)>) -> JoinHandle<SystemThreadCommand> { let name = system_name.clone(); let config = system_config.clone(); @@ -80,16 +196,63 @@ fn spawn_system(system_name : &String, system_config: config::System, waker: mps .name(format!("seance_{}", &name)) .spawn(move || -> _ { let thread_local_runtime = runtime::Builder::new_current_thread().enable_all().build().unwrap(); + let dup_waker = waker.clone(); - // TODO: allow system manager runtime to return a command thread_local_runtime.block_on(async { - let mut system = Manager::new(name, config); + let mut system = Manager::new(name.clone(), config, waker); system.start_clients().await; }); - let _ = waker.send(()); + let _ = dup_waker.send((name, SystemUiEvent::SystemClose)); SystemThreadCommand::Restart }).unwrap() } +fn update_ui(ui_state: &UiState, config: &config::Config) { + crossterm::execute!(io::stdout(), Clear(ClearType::FromCursorUp)).unwrap(); + crossterm::execute!(io::stdout(), MoveTo(0, 0)).unwrap(); + + let (width, height) = crossterm::terminal::size().unwrap(); + let status_lines = (ui_state.systems.len() * 2) + ui_state.systems.values().map(|system| match system { + SystemState::Running(members) => members.len(), + SystemState::Reloading => 1, + SystemState::Restarting => 1, + SystemState::Shutdown => 1, + } ).sum::<usize>() + 1; + + let log_space = height as usize - status_lines - 1; + let log_height = ui_state.logs.len(); + + for (name, state) in ui_state.systems.iter() { + println!("{name}"); + match state { + SystemState::Shutdown => println!(" - [System stopped]"), + SystemState::Reloading => println!(" - [System reloading]"), + SystemState::Restarting => println!(" - [System restarting]"), + SystemState::Running(members) => for (name, state) in members { + if !state.connected { + println!(" - {name} (connecting)") + } else if state.autoproxied { + println!(" - {name} (autoproxy)") + } else { + println!(" - {name}") + } + }, + } + + println!(""); + } + + println!("{:-<width$}", "", width = width as usize); + + let range = if log_height <= log_space { + 0..log_height + } else { + log_height - log_space .. log_height + }; + + for line in ui_state.logs.range(range) { + println!("{line}"); + } +} |