feat/secrets #111
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1872,6 +1872,7 @@ name = "harmony_cli" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "assert_cmd", | ||||
|  "chrono", | ||||
|  "clap", | ||||
|  "console", | ||||
|  "env_logger", | ||||
|  | ||||
| @ -22,7 +22,7 @@ readme = "README.md" | ||||
| license = "GNU AGPL v3" | ||||
| 
 | ||||
| [workspace.dependencies] | ||||
| log = "0.4" | ||||
| log = { version = "0.4", features = ["kv"] } | ||||
| env_logger = "0.11" | ||||
| derive-new = "0.7" | ||||
| async-trait = "0.1" | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								examples/application_monitoring_with_tenant/harmony
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								examples/application_monitoring_with_tenant/harmony
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -8,7 +8,6 @@ use harmony::{ | ||||
|     hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, | ||||
|     infra::opnsense::OPNSenseManagementInterface, | ||||
|     inventory::Inventory, | ||||
|     maestro::Maestro, | ||||
|     modules::{ | ||||
|         http::StaticFilesHttpScore, | ||||
|         ipxe::IpxeScore, | ||||
| @ -130,8 +129,11 @@ async fn main() { | ||||
|         "./data/watchguard/pxe-http-files".to_string(), | ||||
|     )); | ||||
|     let ipxe_score = IpxeScore::new(); | ||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||
|     maestro.register_all(vec![ | ||||
| 
 | ||||
|     harmony_tui::run( | ||||
|         inventory, | ||||
|         topology, | ||||
|         vec![ | ||||
|             Box::new(dns_score), | ||||
|             Box::new(bootstrap_dhcp_score), | ||||
|             Box::new(bootstrap_load_balancer_score), | ||||
| @ -140,6 +142,8 @@ async fn main() { | ||||
|             Box::new(http_score), | ||||
|             Box::new(ipxe_score), | ||||
|             Box::new(dhcp_score), | ||||
|     ]); | ||||
|     harmony_tui::init(maestro).await.unwrap(); | ||||
|         ], | ||||
|     ) | ||||
|     .await | ||||
|     .unwrap(); | ||||
| } | ||||
|  | ||||
| @ -8,7 +8,6 @@ use harmony::{ | ||||
|     hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, | ||||
|     infra::opnsense::OPNSenseManagementInterface, | ||||
|     inventory::Inventory, | ||||
|     maestro::Maestro, | ||||
|     modules::{ | ||||
|         dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||
|         http::StaticFilesHttpScore, | ||||
| @ -84,8 +83,11 @@ async fn main() { | ||||
|     let http_score = StaticFilesHttpScore::new(Url::LocalFolder( | ||||
|         "./data/watchguard/pxe-http-files".to_string(), | ||||
|     )); | ||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||
|     maestro.register_all(vec![ | ||||
| 
 | ||||
|     harmony_tui::run( | ||||
|         inventory, | ||||
|         topology, | ||||
|         vec![ | ||||
|             Box::new(dns_score), | ||||
|             Box::new(dhcp_score), | ||||
|             Box::new(load_balancer_score), | ||||
| @ -98,6 +100,8 @@ async fn main() { | ||||
|             Box::new(SuccessScore {}), | ||||
|             Box::new(ErrorScore {}), | ||||
|             Box::new(PanicScore {}), | ||||
|     ]); | ||||
|     harmony_tui::init(maestro).await.unwrap(); | ||||
|         ], | ||||
|     ) | ||||
|     .await | ||||
|     .unwrap(); | ||||
| } | ||||
|  | ||||
| @ -2,7 +2,6 @@ use std::net::{SocketAddr, SocketAddrV4}; | ||||
| 
 | ||||
| use harmony::{ | ||||
|     inventory::Inventory, | ||||
|     maestro::Maestro, | ||||
|     modules::{ | ||||
|         dns::DnsScore, | ||||
|         dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||
| @ -16,18 +15,19 @@ use harmony_macros::ipv4; | ||||
| 
 | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
|     let inventory = Inventory::autoload(); | ||||
|     let topology = DummyInfra {}; | ||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||
| 
 | ||||
|     maestro.register_all(vec![ | ||||
|     harmony_tui::run( | ||||
|         Inventory::autoload(), | ||||
|         DummyInfra {}, | ||||
|         vec![ | ||||
|             Box::new(SuccessScore {}), | ||||
|             Box::new(ErrorScore {}), | ||||
|             Box::new(PanicScore {}), | ||||
|             Box::new(DnsScore::new(vec![], None)), | ||||
|             Box::new(build_large_score()), | ||||
|     ]); | ||||
|     harmony_tui::init(maestro).await.unwrap(); | ||||
|         ], | ||||
|     ) | ||||
|     .await | ||||
|     .unwrap(); | ||||
| } | ||||
| 
 | ||||
| fn build_large_score() -> LoadBalancerScore { | ||||
|  | ||||
| @ -241,7 +241,7 @@ pub struct DummyInfra; | ||||
| #[async_trait] | ||||
| impl Topology for DummyInfra { | ||||
|     fn name(&self) -> &str { | ||||
|         todo!() | ||||
|         "DummyInfra" | ||||
|     } | ||||
| 
 | ||||
|     async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> { | ||||
|  | ||||
| @ -22,6 +22,7 @@ indicatif = "0.18.0" | ||||
| lazy_static = "1.5.0" | ||||
| log.workspace = true | ||||
| indicatif-log-bridge = "0.2.3" | ||||
| chrono.workspace = true | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| harmony = { path = "../harmony", features = ["testing"] } | ||||
|  | ||||
| @ -1,22 +1,17 @@ | ||||
| use chrono::Local; | ||||
| use console::style; | ||||
| use harmony::{ | ||||
|     instrumentation::{self, HarmonyEvent}, | ||||
|     modules::application::ApplicationFeatureStatus, | ||||
|     topology::TopologyStatus, | ||||
| }; | ||||
| use indicatif::MultiProgress; | ||||
| use indicatif_log_bridge::LogWrapper; | ||||
| use log::error; | ||||
| use std::{ | ||||
|     sync::{Arc, Mutex}, | ||||
|     thread, | ||||
|     time::Duration, | ||||
| }; | ||||
| 
 | ||||
| use crate::progress::{IndicatifProgressTracker, ProgressTracker}; | ||||
| use log::{error, info, log_enabled}; | ||||
| use std::io::Write; | ||||
| use std::sync::{Arc, Mutex}; | ||||
| 
 | ||||
| pub fn init() -> tokio::task::JoinHandle<()> { | ||||
|     let base_progress = configure_logger(); | ||||
|     let handle = tokio::spawn(handle_events(base_progress)); | ||||
|     configure_logger(); | ||||
|     let handle = tokio::spawn(handle_events()); | ||||
| 
 | ||||
|     loop { | ||||
|         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { | ||||
| @ -27,28 +22,76 @@ pub fn init() -> tokio::task::JoinHandle<()> { | ||||
|     handle | ||||
| } | ||||
| 
 | ||||
| fn configure_logger() -> MultiProgress { | ||||
|     let logger = | ||||
|         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); | ||||
|     let level = logger.filter(); | ||||
|     let progress = MultiProgress::new(); | ||||
| fn configure_logger() { | ||||
|     env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) | ||||
|         .format(|buf, record| { | ||||
|             let debug_mode = log_enabled!(log::Level::Debug); | ||||
|             let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S"); | ||||
| 
 | ||||
|     LogWrapper::new(progress.clone(), logger) | ||||
|         .try_init() | ||||
|         .unwrap(); | ||||
|     log::set_max_level(level); | ||||
| 
 | ||||
|     progress | ||||
|             let level = match record.level() { | ||||
|                 log::Level::Error => style("ERROR").red(), | ||||
|                 log::Level::Warn => style("WARN").yellow(), | ||||
|                 log::Level::Info => style("INFO").green(), | ||||
|                 log::Level::Debug => style("DEBUG").blue(), | ||||
|                 log::Level::Trace => style("TRACE").magenta(), | ||||
|             }; | ||||
|             if let Some(status) = record.key_values().get(log::kv::Key::from("status")) { | ||||
|                 let status = status.to_borrowed_str().unwrap(); | ||||
|                 let emoji = match status { | ||||
|                     "finished" => style(crate::theme::EMOJI_SUCCESS.to_string()).green(), | ||||
|                     "skipped" => style(crate::theme::EMOJI_SKIP.to_string()).yellow(), | ||||
|                     "failed" => style(crate::theme::EMOJI_ERROR.to_string()).red(), | ||||
|                     _ => style("".into()), | ||||
|                 }; | ||||
|                 if debug_mode { | ||||
|                     writeln!( | ||||
|                         buf, | ||||
|                         "[{} {:<5} {}] {} {}", | ||||
|                         timestamp, | ||||
|                         level, | ||||
|                         record.target(), | ||||
|                         emoji, | ||||
|                         record.args() | ||||
|                     ) | ||||
|                 } else { | ||||
|                     writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args()) | ||||
|                 } | ||||
|             } else if let Some(emoji) = record.key_values().get(log::kv::Key::from("emoji")) { | ||||
|                 if debug_mode { | ||||
|                     writeln!( | ||||
|                         buf, | ||||
|                         "[{} {:<5} {}] {} {}", | ||||
|                         timestamp, | ||||
|                         level, | ||||
|                         record.target(), | ||||
|                         emoji, | ||||
|                         record.args() | ||||
|                     ) | ||||
|                 } else { | ||||
|                     writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args()) | ||||
|                 } | ||||
|             } else if debug_mode { | ||||
|                 writeln!( | ||||
|                     buf, | ||||
|                     "[{} {:<5} {}] {}", | ||||
|                     timestamp, | ||||
|                     level, | ||||
|                     record.target(), | ||||
|                     record.args() | ||||
|                 ) | ||||
|             } else { | ||||
|                 writeln!(buf, "[{:<5}] {}", level, record.args()) | ||||
|             } | ||||
|         }) | ||||
|         .init(); | ||||
| } | ||||
| 
 | ||||
| async fn handle_events(base_progress: MultiProgress) { | ||||
|     let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone())); | ||||
| async fn handle_events() { | ||||
|     let preparing_topology = Arc::new(Mutex::new(false)); | ||||
|     let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None)); | ||||
| 
 | ||||
|     instrumentation::subscribe("Harmony CLI Logger", { | ||||
|         move |event| { | ||||
|             let progress_tracker = Arc::clone(&progress_tracker); | ||||
|             let preparing_topology = Arc::clone(&preparing_topology); | ||||
|             let current_score = Arc::clone(¤t_score); | ||||
| 
 | ||||
| @ -59,90 +102,57 @@ async fn handle_events(base_progress: MultiProgress) { | ||||
|                 match event { | ||||
|                     HarmonyEvent::HarmonyStarted => {} | ||||
|                     HarmonyEvent::HarmonyFinished => { | ||||
|                         progress_tracker.add_section( | ||||
|                             "harmony-summary", | ||||
|                             &format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY), | ||||
|                         ); | ||||
|                         progress_tracker.add_section("harmony-finished", "\n\n"); | ||||
|                         thread::sleep(Duration::from_millis(200)); | ||||
|                         let emoji = crate::theme::EMOJI_HARMONY.to_string(); | ||||
|                         info!(emoji = emoji.as_str(); "Harmony completed"); | ||||
|                         return false; | ||||
|                     } | ||||
|                     HarmonyEvent::TopologyStateChanged { | ||||
|                         topology, | ||||
|                         status, | ||||
|                         message, | ||||
|                     } => { | ||||
|                         let section_key = topology_key(&topology); | ||||
| 
 | ||||
|                         match status { | ||||
|                     } => match status { | ||||
|                         TopologyStatus::Queued => {} | ||||
|                         TopologyStatus::Preparing => { | ||||
|                                 progress_tracker.add_section( | ||||
|                                     §ion_key, | ||||
|                                     &format!( | ||||
|                                         "\n{} Preparing environment: {topology}...", | ||||
|                                         crate::theme::EMOJI_TOPOLOGY | ||||
|                                     ), | ||||
|                                 ); | ||||
|                             let emoji = format!("{}", style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow()); | ||||
|                             info!(emoji = emoji.as_str(); "Preparing environment: {topology}..."); | ||||
|                             (*preparing_topology) = true; | ||||
|                         } | ||||
|                         TopologyStatus::Success => { | ||||
|                             (*preparing_topology) = false; | ||||
|                                 progress_tracker.add_task(§ion_key, "topology-success", ""); | ||||
|                                 progress_tracker | ||||
|                                     .finish_task("topology-success", &message.unwrap_or("".into())); | ||||
|                             if let Some(message) = message { | ||||
|                                 info!(status = "finished"; "{message}"); | ||||
|                             } | ||||
|                         } | ||||
|                         TopologyStatus::Noop => { | ||||
|                             (*preparing_topology) = false; | ||||
|                                 progress_tracker.add_task(§ion_key, "topology-skip", ""); | ||||
|                                 progress_tracker | ||||
|                                     .skip_task("topology-skip", &message.unwrap_or("".into())); | ||||
|                             if let Some(message) = message { | ||||
|                                 info!(status = "skipped"; "{message}"); | ||||
|                             } | ||||
|                         } | ||||
|                         TopologyStatus::Error => { | ||||
|                                 progress_tracker.add_task(§ion_key, "topology-error", ""); | ||||
|                             (*preparing_topology) = false; | ||||
|                                 progress_tracker | ||||
|                                     .fail_task("topology-error", &message.unwrap_or("".into())); | ||||
|                             } | ||||
|                             if let Some(message) = message { | ||||
|                                 error!(status = "failed"; "{message}"); | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     HarmonyEvent::InterpretExecutionStarted { | ||||
|                         execution_id: task_key, | ||||
|                         topology, | ||||
|                         execution_id: _, | ||||
|                         topology: _, | ||||
|                         interpret: _, | ||||
|                         score, | ||||
|                         message, | ||||
|                     } => { | ||||
|                         let is_key_topology = (*preparing_topology) | ||||
|                             && progress_tracker.contains_section(&topology_key(&topology)); | ||||
|                         let is_key_current_score = current_score.is_some() | ||||
|                             && progress_tracker | ||||
|                                 .contains_section(&score_key(¤t_score.clone().unwrap())); | ||||
|                         let is_key_score = progress_tracker.contains_section(&score_key(&score)); | ||||
| 
 | ||||
|                         let section_key = if is_key_topology { | ||||
|                             topology_key(&topology) | ||||
|                         } else if is_key_current_score { | ||||
|                             score_key(¤t_score.clone().unwrap()) | ||||
|                         } else if is_key_score { | ||||
|                             score_key(&score) | ||||
|                         if *preparing_topology || current_score.is_some() { | ||||
|                             info!("{message}"); | ||||
|                         } else { | ||||
|                             (*current_score) = Some(score.clone()); | ||||
|                             let key = score_key(&score); | ||||
|                             progress_tracker.add_section( | ||||
|                                 &key, | ||||
|                                 &format!( | ||||
|                                     "{} Interpreting score: {score}...", | ||||
|                                     crate::theme::EMOJI_SCORE | ||||
|                                 ), | ||||
|                             ); | ||||
|                             key | ||||
|                         }; | ||||
| 
 | ||||
|                         progress_tracker.add_task(§ion_key, &task_key, &message); | ||||
|                             let emoji = format!("{}", style(crate::theme::EMOJI_SCORE).blue()); | ||||
|                             info!(emoji = emoji.as_str(); "Interpreting score: {score}..."); | ||||
|                         } | ||||
|                     } | ||||
|                     HarmonyEvent::InterpretExecutionFinished { | ||||
|                         execution_id: task_key, | ||||
|                         execution_id: _, | ||||
|                         topology: _, | ||||
|                         interpret: _, | ||||
|                         score, | ||||
| @ -155,16 +165,17 @@ async fn handle_events(base_progress: MultiProgress) { | ||||
|                         match outcome { | ||||
|                             Ok(outcome) => match outcome.status { | ||||
|                                 harmony::interpret::InterpretStatus::SUCCESS => { | ||||
|                                     progress_tracker.finish_task(&task_key, &outcome.message); | ||||
|                                     info!(status = "finished"; "{}", outcome.message); | ||||
|                                 } | ||||
|                                 harmony::interpret::InterpretStatus::NOOP => { | ||||
|                                     progress_tracker.skip_task(&task_key, &outcome.message); | ||||
|                                     info!(status = "skipped"; "{}", outcome.message); | ||||
|                                 } | ||||
|                                 _ => { | ||||
|                                     error!(status = "failed"; "{}", outcome.message); | ||||
|                                 } | ||||
|                                 _ => progress_tracker.fail_task(&task_key, &outcome.message), | ||||
|                             }, | ||||
|                             Err(err) => { | ||||
|                                 error!("Interpret error: {err}"); | ||||
|                                 progress_tracker.fail_task(&task_key, &err.to_string()); | ||||
|                                 error!(status = "failed"; "{}", err); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| @ -173,30 +184,17 @@ async fn handle_events(base_progress: MultiProgress) { | ||||
|                         application, | ||||
|                         feature, | ||||
|                         status, | ||||
|                     } => { | ||||
|                         if let Some(score) = &(*current_score) { | ||||
|                             let section_key = score_key(score); | ||||
|                             let task_key = app_feature_key(&application, &feature); | ||||
| 
 | ||||
|                             match status { | ||||
|                     } => match status { | ||||
|                         ApplicationFeatureStatus::Installing => { | ||||
|                                     let message = format!("Feature '{}' installing...", feature); | ||||
|                                     progress_tracker.add_task(§ion_key, &task_key, &message); | ||||
|                             info!("Installing feature '{}' for '{}'...", feature, application); | ||||
|                         } | ||||
|                         ApplicationFeatureStatus::Installed => { | ||||
|                                     let message = format!("Feature '{}' installed", feature); | ||||
|                                     progress_tracker.finish_task(&task_key, &message); | ||||
|                             info!(status = "finished"; "Feature '{}' installed", feature); | ||||
|                         } | ||||
|                         ApplicationFeatureStatus::Failed { details } => { | ||||
|                                     let message = format!( | ||||
|                                         "Feature '{}' installation failed: {}", | ||||
|                                         feature, details | ||||
|                                     ); | ||||
|                                     progress_tracker.fail_task(&task_key, &message); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                             error!(status = "failed"; "Feature '{}' installation failed: {}", feature, details); | ||||
|                         } | ||||
|                     }, | ||||
|                 } | ||||
|                 true | ||||
|             } | ||||
| @ -204,15 +202,3 @@ async fn handle_events(base_progress: MultiProgress) { | ||||
|     }) | ||||
|     .await; | ||||
| } | ||||
| 
 | ||||
| fn topology_key(topology: &str) -> String { | ||||
|     format!("topology-{topology}") | ||||
| } | ||||
| 
 | ||||
| fn score_key(score: &str) -> String { | ||||
|     format!("score-{score}") | ||||
| } | ||||
| 
 | ||||
| fn app_feature_key(application: &str, feature: &str) -> String { | ||||
|     format!("app-{application}-{feature}") | ||||
| } | ||||
|  | ||||
| @ -90,13 +90,37 @@ pub async fn run<T: Topology + Send + Sync + 'static>( | ||||
|     topology: T, | ||||
|     scores: Vec<Box<dyn Score<T>>>, | ||||
|     args_struct: Option<Args>, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let args = match args_struct { | ||||
|         Some(args) => args, | ||||
|         None => Args::parse(), | ||||
|     }; | ||||
| 
 | ||||
|     #[cfg(not(feature = "tui"))] | ||||
|     if args.interactive { | ||||
|         return Err("Not compiled with interactive support".into()); | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(feature = "tui")] | ||||
|     if args.interactive { | ||||
|         return harmony_tui::run(inventory, topology, scores).await; | ||||
|     } | ||||
| 
 | ||||
|     run_cli(inventory, topology, scores, args).await | ||||
| } | ||||
| 
 | ||||
| pub async fn run_cli<T: Topology + Send + Sync + 'static>( | ||||
|     inventory: Inventory, | ||||
|     topology: T, | ||||
|     scores: Vec<Box<dyn Score<T>>>, | ||||
|     args: Args, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let cli_logger_handle = cli_logger::init(); | ||||
| 
 | ||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||
|     maestro.register_all(scores); | ||||
| 
 | ||||
|     let result = init(maestro, args_struct).await; | ||||
|     let result = init(maestro, args).await; | ||||
| 
 | ||||
|     instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap(); | ||||
|     let _ = tokio::try_join!(cli_logger_handle); | ||||
| @ -105,23 +129,8 @@ pub async fn run<T: Topology + Send + Sync + 'static>( | ||||
| 
 | ||||
| async fn init<T: Topology + Send + Sync + 'static>( | ||||
|     maestro: harmony::maestro::Maestro<T>, | ||||
|     args_struct: Option<Args>, | ||||
|     args: Args, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let args = match args_struct { | ||||
|         Some(args) => args, | ||||
|         None => Args::parse(), | ||||
|     }; | ||||
| 
 | ||||
|     #[cfg(feature = "tui")] | ||||
|     if args.interactive { | ||||
|         return harmony_tui::init(maestro).await; | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(not(feature = "tui"))] | ||||
|     if args.interactive { | ||||
|         return Err("Not compiled with interactive support".into()); | ||||
|     } | ||||
| 
 | ||||
|     let _ = env_logger::builder().try_init(); | ||||
| 
 | ||||
|     let scores_vec = maestro_scores_filter(&maestro, args.all, args.filter, args.number); | ||||
| @ -193,14 +202,14 @@ mod tests { | ||||
|         let maestro = init_test_maestro(); | ||||
|         let res = crate::init( | ||||
|             maestro, | ||||
|             Some(crate::Args { | ||||
|             crate::Args { | ||||
|                 yes: true, | ||||
|                 filter: Some("SuccessScore".to_owned()), | ||||
|                 interactive: false, | ||||
|                 all: true, | ||||
|                 number: 0, | ||||
|                 list: false, | ||||
|             }), | ||||
|             }, | ||||
|         ) | ||||
|         .await; | ||||
| 
 | ||||
| @ -213,14 +222,14 @@ mod tests { | ||||
| 
 | ||||
|         let res = crate::init( | ||||
|             maestro, | ||||
|             Some(crate::Args { | ||||
|             crate::Args { | ||||
|                 yes: true, | ||||
|                 filter: Some("ErrorScore".to_owned()), | ||||
|                 interactive: false, | ||||
|                 all: true, | ||||
|                 number: 0, | ||||
|                 list: false, | ||||
|             }), | ||||
|             }, | ||||
|         ) | ||||
|         .await; | ||||
| 
 | ||||
| @ -233,14 +242,14 @@ mod tests { | ||||
| 
 | ||||
|         let res = crate::init( | ||||
|             maestro, | ||||
|             Some(crate::Args { | ||||
|             crate::Args { | ||||
|                 yes: true, | ||||
|                 filter: None, | ||||
|                 interactive: false, | ||||
|                 all: false, | ||||
|                 number: 0, | ||||
|                 list: false, | ||||
|             }), | ||||
|             }, | ||||
|         ) | ||||
|         .await; | ||||
| 
 | ||||
|  | ||||
| @ -9,7 +9,13 @@ use widget::{help::HelpWidget, score::ScoreListWidget}; | ||||
| use std::{panic, sync::Arc, time::Duration}; | ||||
| 
 | ||||
| use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind}; | ||||
| use harmony::{maestro::Maestro, score::Score, topology::Topology}; | ||||
| use harmony::{ | ||||
|     instrumentation::{self, HarmonyEvent}, | ||||
|     inventory::Inventory, | ||||
|     maestro::Maestro, | ||||
|     score::Score, | ||||
|     topology::Topology, | ||||
| }; | ||||
| use ratatui::{ | ||||
|     self, Frame, | ||||
|     layout::{Constraint, Layout, Position}, | ||||
| @ -39,22 +45,62 @@ pub mod tui { | ||||
| ///
 | ||||
| /// #[tokio::main]
 | ||||
| /// async fn main() {
 | ||||
| ///     let inventory = Inventory::autoload();
 | ||||
| ///     let topology = HAClusterTopology::autoload();
 | ||||
| ///     let mut maestro = Maestro::new_without_initialization(inventory, topology);
 | ||||
| ///
 | ||||
| ///     maestro.register_all(vec![
 | ||||
| ///     harmony_tui::run(
 | ||||
| ///         Inventory::autoload(),
 | ||||
| ///         HAClusterTopology::autoload(),
 | ||||
| ///         vec![
 | ||||
| ///             Box::new(SuccessScore {}),
 | ||||
| ///             Box::new(ErrorScore {}),
 | ||||
| ///             Box::new(PanicScore {}),
 | ||||
| ///     ]);
 | ||||
| ///     harmony_tui::init(maestro).await.unwrap();
 | ||||
| ///         ]
 | ||||
| ///     ).await.unwrap();
 | ||||
| /// }
 | ||||
| /// ```
 | ||||
| pub async fn init<T: Topology + Send + Sync + 'static>( | ||||
| pub async fn run<T: Topology + Send + Sync + 'static>( | ||||
|     inventory: Inventory, | ||||
|     topology: T, | ||||
|     scores: Vec<Box<dyn Score<T>>>, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let handle = init_instrumentation().await; | ||||
| 
 | ||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||
|     maestro.register_all(scores); | ||||
| 
 | ||||
|     let result = init(maestro).await; | ||||
| 
 | ||||
|     let _ = tokio::try_join!(handle); | ||||
|     result | ||||
| } | ||||
| 
 | ||||
| async fn init<T: Topology + Send + Sync + 'static>( | ||||
|     maestro: Maestro<T>, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     HarmonyTUI::new(maestro).init().await | ||||
|     let result = HarmonyTUI::new(maestro).init().await; | ||||
| 
 | ||||
|     instrumentation::instrument(HarmonyEvent::HarmonyFinished).unwrap(); | ||||
|     result | ||||
| } | ||||
| 
 | ||||
| async fn init_instrumentation() -> tokio::task::JoinHandle<()> { | ||||
|     let handle = tokio::spawn(handle_harmony_events()); | ||||
| 
 | ||||
|     loop { | ||||
|         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     handle | ||||
| } | ||||
| 
 | ||||
| async fn handle_harmony_events() { | ||||
|     instrumentation::subscribe("Harmony TUI Logger", async |event| { | ||||
|         if let HarmonyEvent::HarmonyFinished = event { | ||||
|             return false; | ||||
|         }; | ||||
|         true | ||||
|     }) | ||||
|     .await; | ||||
| } | ||||
| 
 | ||||
| pub struct HarmonyTUI<T: Topology> { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user