diff --git a/dozer-cli/src/cli/types.rs b/dozer-cli/src/cli/types.rs index a19fd0c862..118b2a1768 100644 --- a/dozer-cli/src/cli/types.rs +++ b/dozer-cli/src/cli/types.rs @@ -76,6 +76,23 @@ pub enum Commands { Security(Security), #[command(about = "Deploy cloud applications")] Cloud(Cloud), + #[command(about = "Run UI server")] + UI(UI), +} + +#[derive(Debug, Args)] +pub struct UI { + #[command(subcommand)] + pub command: Option, +} + +#[derive(Debug, Subcommand)] +pub enum UICommands { + #[command( + about = "Updates the latest UI code", + long_about = "Updates the latest UI code" + )] + Update, } #[derive(Debug, Args)] @@ -121,11 +138,6 @@ pub enum RunCommands { long_about = "Run lambda functions. Lambda functions are JavaScript or Python functions that are called when a new operation is output." )] Lambda, - #[command( - about = "Open web interface", - long_about = "Open web interface. Web interface is used to interact with dozer" - )] - AppUI, } #[derive(Debug, Args)] diff --git a/dozer-cli/src/main.rs b/dozer-cli/src/main.rs index 8b4019ef32..2cbeb355e1 100644 --- a/dozer-cli/src/main.rs +++ b/dozer-cli/src/main.rs @@ -1,18 +1,22 @@ use clap::Parser; use dozer_api::shutdown; use dozer_cli::cli::cloud::CloudCommands; -use dozer_cli::cli::types::{Cli, Commands, ConnectorCommand, RunCommands, SecurityCommands}; +use dozer_cli::cli::types::{ + Cli, Commands, ConnectorCommand, RunCommands, SecurityCommands, UICommands, +}; use dozer_cli::cli::{generate_config_repl, init_config}; use dozer_cli::cli::{init_dozer, list_sources}; use dozer_cli::cloud::{cloud_app_context::CloudAppContext, CloudClient, DozerGrpcCloudClient}; use dozer_cli::errors::{CliError, CloudError, OrchestrationError}; use dozer_cli::ui; +use dozer_cli::ui::app::AppUIError; use dozer_cli::{set_ctrl_handler, set_panic_hook, ui::live}; use dozer_tracing::LabelsAndProgress; use dozer_types::models::config::Config; use dozer_types::models::telemetry::{TelemetryConfig, TelemetryMetricsConfig}; use dozer_types::tracing::{error, error_span, info}; use futures::stream::{AbortHandle, Abortable}; +use futures::TryFutureExt; use std::convert::identity; use std::process; use std::sync::Arc; @@ -40,6 +44,7 @@ fn run() -> Result<(), OrchestrationError> { set_panic_hook(); let config_res = init_configuration(&cli, runtime.clone()); + // Now we have access to telemetry configuration. Telemetry must be initialized in tokio runtime. let app_id = config_res .as_ref() @@ -61,6 +66,24 @@ fn run() -> Result<(), OrchestrationError> { let _telemetry = runtime.block_on(async { Telemetry::new(app_id, &telemetry_config) }); + // running UI does not require config to be loaded + if let Commands::UI(run) = &cli.cmd { + if let Some(UICommands::Update) = run.command { + runtime.block_on( + ui::downloader::fetch_latest_dozer_app_ui_code() + .map_err(AppUIError::DownloaderError), + )?; + info!("Run `dozer ui` to see the changes."); + } else { + runtime.block_on(ui::app::start_app_ui_server( + &runtime, + shutdown_receiver, + false, + ))?; + } + return Ok(()); + } + // Run Cloud if let Commands::Cloud(cloud) = &cli.cmd { return run_cloud(cloud, runtime, &cli); @@ -85,14 +108,6 @@ fn run() -> Result<(), OrchestrationError> { Some(RunCommands::Lambda) => { dozer.runtime.block_on(dozer.run_lambda(shutdown_receiver)) } - Some(RunCommands::AppUI) => { - dozer.runtime.block_on(ui::app::start_app_ui_server( - &dozer.runtime, - shutdown_receiver, - false, - ))?; - Ok(()) - } None => dozer .runtime .block_on(dozer.run_all(shutdown_receiver, run.locked)), @@ -134,6 +149,9 @@ fn run() -> Result<(), OrchestrationError> { .and_then(identity) }), Commands::Clean => dozer.clean(), + Commands::UI(_) => { + panic!("This should not happen as it is handled earlier"); + } Commands::Cloud(_) => { panic!("This should not happen as it is handled earlier"); } diff --git a/dozer-cli/src/ui/app/mod.rs b/dozer-cli/src/ui/app/mod.rs index 0f04a3d58b..84933fc7d4 100644 --- a/dozer-cli/src/ui/app/mod.rs +++ b/dozer-cli/src/ui/app/mod.rs @@ -24,15 +24,24 @@ pub async fn start_app_ui_server( let state = Arc::new(AppUIState::new()); state.set_sender(sender.clone()).await; // Ignore if build fails - let _ = state.build(runtime.clone()).await; + let res = state.build(runtime.clone()).await; + if let Err(e) = res { + info!("Failed to build state : {}", e); + } let state2: Arc = state.clone(); if !disable_ui { - downloader::fetch_latest_dozer_app_ui_code().await?; + info!("Check if latest app ui code is available"); + let already_exist = downloader::validate_if_dozer_app_ui_code_exists(); + if !already_exist { + info!("There's no ui code folder, fetching latest app ui code"); + downloader::fetch_latest_dozer_app_ui_code().await?; + } let react_app_server: dozer_api::actix_web::dev::Server = downloader::start_react_app(APP_UI_WEB_PORT, LOCAL_APP_UI_DIR) .map_err(AppUIError::CannotStartUiServer)?; tokio::spawn(react_app_server); let browser_url: String = format!("http://localhost:{}", APP_UI_WEB_PORT); + info!("Starting ui on : {}", browser_url); if webbrowser::open(&browser_url).is_err() { info!("Failed to open browser. "); } @@ -54,6 +63,5 @@ pub async fn start_app_ui_server( res.unwrap(); }); watcher::watch(runtime, state.clone(), shutdown).await?; - Ok(()) } diff --git a/dozer-cli/src/ui/app/state.rs b/dozer-cli/src/ui/app/state.rs index e796ce125f..3483c20386 100644 --- a/dozer-cli/src/ui/app/state.rs +++ b/dozer-cli/src/ui/app/state.rs @@ -124,7 +124,6 @@ impl AppUIState { let mut lock = self.dozer.write().await; let cli = Cli::parse(); - let (config, _) = init_config( cli.config_paths.clone(), cli.config_token.clone(), @@ -132,6 +131,7 @@ impl AppUIState { cli.ignore_pipe, ) .await?; + let dozer = init_dozer(runtime, config, Default::default())?; let contract = create_contract(dozer.clone()).await; diff --git a/dozer-cli/src/ui/downloader.rs b/dozer-cli/src/ui/downloader.rs index 2b725e0b69..b916ac6b6b 100644 --- a/dozer-cli/src/ui/downloader.rs +++ b/dozer-cli/src/ui/downloader.rs @@ -47,15 +47,15 @@ async fn fetch_dozer_ui(url: &str, folder_name: &str) -> Result<(), DownloaderEr let prev_zip_file_name = existing_key.as_str(); if key_changed { info!("Downloading latest file: {}", zip_file_name); - let base_url = &format!("{}/", url); let zip_url = &(base_url.to_owned() + zip_file_name); if !prev_zip_file_name.is_empty() { delete_file_if_present(prev_zip_file_name, folder_name)?; } get_zip_from_url(zip_url, folder_name, zip_file_name).await?; + } else { + info!("Current file is up to date"); } - Ok(()) } pub const LOCAL_APP_UI_DIR: &str = "local-app-ui"; @@ -67,6 +67,14 @@ pub async fn fetch_latest_dozer_app_ui_code() -> Result<(), DownloaderError> { .await } +pub fn validate_if_dozer_app_ui_code_exists() -> bool { + let directory_path = get_directory_path(); + let file_path = Path::new(&directory_path) + .join(LOCAL_APP_UI_DIR) + .join("contents"); + file_path.exists() +} + pub const LIVE_APP_UI_DIR: &str = "live-app-ui"; pub async fn fetch_latest_dozer_explorer_code() -> Result<(), DownloaderError> { fetch_dozer_ui( diff --git a/dozer-cli/src/ui/mod.rs b/dozer-cli/src/ui/mod.rs index 1dc73e6b1c..03dd71a21f 100644 --- a/dozer-cli/src/ui/mod.rs +++ b/dozer-cli/src/ui/mod.rs @@ -1,3 +1,3 @@ pub mod app; -mod downloader; +pub mod downloader; pub mod live;