//! CH-J Server Manager 0.1.1 — Rust + Tauri 2 Core. //! //! Layers: `src/api` (TypeScript CH-J API) → `commands` (Tauri commands, caller checks in //! `security`) → `core` (services). See `docs/architecture-0.1.1.md`. pub mod bootstrap; pub mod commands; pub mod core; pub mod dialogs; pub mod protocol; pub mod security; pub mod windows; use crate::commands::license::LicenseSlot; use crate::core::app::CoreSlot; use crate::security::caller::{LICENSE_LABEL, MAIN_LABEL, MODULE_LABEL_PREFIX}; use std::time::Duration; use tauri::{Manager, RunEvent, WindowEvent}; fn focus_main(app: &tauri::AppHandle) { if let Some(window) = app.get_webview_window(LICENSE_LABEL) { let _ = window.show(); let _ = window.set_focus(); return; } if let Some(window) = app.get_webview_window(MAIN_LABEL) { let _ = window.unminimize(); let _ = window.show(); let _ = window.set_focus(); } } fn on_window_event(window: &tauri::Window, event: &WindowEvent) { let app = window.app_handle().clone(); let label = window.label().to_string(); let core = app.state::().try_get(); match event { WindowEvent::Destroyed => { if label == LICENSE_LABEL { bootstrap::license_window_closed(&app); } else if label == MAIN_LABEL { if let Some(core) = &core { core.services.diagnostics.stop_all(); core.services.modules.close_all(core); } if !cfg!(target_os = "macos") { app.exit(0); } } else if label.starts_with(MODULE_LABEL_PREFIX) { if let Some(core) = &core { core.services.modules.on_destroyed(core, &label); } } } WindowEvent::Focused(false) if label == MAIN_LABEL || label.starts_with(MODULE_LABEL_PREFIX) => { if let Some(core) = core { let app = app.clone(); tauri::async_runtime::spawn(async move { // Ignore focus moving between our own windows or to the OS biometric prompt. loop { tokio::time::sleep(Duration::from_millis(250)).await; if !core.services.biometrics.is_active() { break; } } let any_focused = app.webview_windows().values().any(|w| w.is_focused().unwrap_or(false)); if !any_focused { core.services.lock.lost_focus(&core).await; } }); } } WindowEvent::Resized(_) => { if let Some(core) = core { if window.is_minimized().unwrap_or(false) { if label == MAIN_LABEL { core.services.modules.minimize_all(&core); } else if label.starts_with(MODULE_LABEL_PREFIX) { let main_minimized = app.get_webview_window(MAIN_LABEL).and_then(|m| m.is_minimized().ok()).unwrap_or(false); core.services.modules.minimize_label(&core, &label, !main_minimized); } } } } _ => {} } } /// Every Core command (plus the test-only `smoke_report` with the `smoke` feature). macro_rules! command_list { ($($extra:path),*) => { tauri::generate_handler![ $($extra,)* commands::app::core_get_info, commands::app::core_legal_open, commands::app::core_open_external, commands::app::core_config_get, commands::app::core_config_update, commands::app::core_i18n_languages, commands::app::window_control, commands::app::window_is_maximized, commands::app::window_activity, commands::vault::core_vault_status, commands::vault::core_vault_create, commands::vault::core_vault_unlock, commands::vault::core_vault_lock, commands::vault::core_vault_reset, commands::vault::core_biometrics_status, commands::vault::core_biometrics_enable, commands::vault::core_biometrics_disable, commands::vault::core_biometrics_unlock, commands::vault::core_biometrics_auto_unlock, commands::vault::core_biometrics_authenticate, commands::profiles::core_profiles_list, commands::profiles::core_profiles_save, commands::profiles::core_profiles_delete, commands::profiles::core_profiles_select_private_key, commands::profiles::core_profiles_export, commands::profiles::core_profiles_preview_import, commands::profiles::core_profiles_commit_import, commands::profiles::core_profiles_cancel_import, commands::availability::core_availability_list, commands::availability::core_availability_refresh, commands::ssh::core_ssh_list, commands::ssh::core_ssh_connect, commands::ssh::core_ssh_trust_host_key, commands::ssh::core_ssh_disconnect, commands::ssh::core_ssh_resize, commands::ssh::core_ssh_input, commands::diagnostics::core_diagnostics_resolve, commands::diagnostics::core_diagnostics_start, commands::diagnostics::core_diagnostics_cancel, commands::diagnostics::core_diagnostics_get, commands::diagnostics::core_diagnostics_history, commands::diagnostics::core_diagnostics_remove_history, commands::diagnostics::core_diagnostics_export, commands::diagnostics::core_diagnostics_copy, commands::diagnostics::core_diagnostics_import, commands::updates::core_updates_get_state, commands::updates::core_updates_check, commands::updates::core_updates_select, commands::updates::core_updates_download, commands::updates::core_updates_install, commands::updates::core_updates_reveal, commands::modules::core_modules_list, commands::modules::core_modules_get_state, commands::modules::core_modules_check_catalog, commands::modules::core_modules_install, commands::modules::core_modules_uninstall, commands::modules::core_modules_open, commands::license::license_read, commands::license::license_show, commands::license::license_accept, commands::license::license_decline, commands::module_api::module_get_info, commands::module_api::module_ui_get_language, commands::module_api::module_close, commands::module_api::module_window_minimize, commands::module_api::module_sessions_list, commands::module_api::module_system_metrics, commands::module_api::module_system_virtual_memory, commands::module_api::module_system_manage_virtual_memory, commands::module_api::module_disks_inspect, commands::module_api::module_disks_smart, commands::module_api::module_disks_plan, commands::module_api::module_disks_auto_expand, commands::module_api::module_disks_cancel_plan, commands::module_api::module_disks_apply, commands::module_api::module_disks_jobs, commands::module_api::module_disks_job_status, commands::module_api::module_keys_generate, commands::module_api::module_keys_save, commands::module_api::module_keys_password, commands::module_api::module_keys_copy_password, commands::module_api::module_logs_read, commands::module_api::module_users_list, commands::module_api::module_users_manage, commands::module_api::module_users_privileges, commands::module_api::module_users_inventory, commands::module_api::module_users_detail, commands::module_api::module_users_keys, commands::module_api::module_users_ssh_policy, commands::module_api::module_nginx_inspect, commands::module_api::module_nginx_read_config, commands::module_api::module_nginx_dump_config, commands::module_api::module_nginx_test_config, commands::module_api::module_nginx_save_config, commands::module_api::module_nginx_reload, commands::module_api::module_files_list, commands::module_api::module_files_privileges, commands::module_api::module_files_read_text, commands::module_api::module_files_write_text, commands::module_api::module_files_save_text, commands::module_api::module_files_list_recovery, commands::module_api::module_files_read_recovery, commands::module_api::module_files_cleanup_recovery, commands::module_api::module_files_close_text, commands::module_api::module_files_create_file, commands::module_api::module_files_mkdir, commands::module_api::module_files_rename, commands::module_api::module_files_remove, commands::module_api::module_files_remove_many, commands::module_api::module_files_upload, commands::module_api::module_files_download, commands::module_api::module_files_download_many, commands::module_api::module_files_download_archive, commands::module_api::module_transfers_list, commands::module_api::module_transfers_plan_upload, commands::module_api::module_transfers_plan_download, commands::module_api::module_transfers_start, commands::module_api::module_transfers_pause, commands::module_api::module_transfers_resume, commands::module_api::module_transfers_retry, commands::module_api::module_transfers_cancel, commands::module_api::module_transfers_remove, commands::module_api::module_transfers_settings, commands::module_api::module_transfers_plan_copy, commands::module_api::module_local_places, commands::module_api::module_local_list, commands::module_api::module_local_mkdir, commands::module_api::module_local_create_file, commands::module_api::module_local_rename, commands::module_api::module_local_remove, commands::module_api::module_hashing_algorithms, commands::module_api::module_hashing_select_files, commands::module_api::module_hashing_select_directory, commands::module_api::module_hashing_select_manifest, commands::module_api::module_hashing_select_manifest_destination, commands::module_api::module_hashing_start, commands::module_api::module_hashing_verify, commands::module_api::module_hashing_compare, commands::module_api::module_hashing_generate_manifest, commands::module_api::module_hashing_verify_manifest, commands::module_api::module_hashing_export_results, commands::module_api::module_hashing_copy_result, commands::module_api::module_hashing_status, commands::module_api::module_hashing_cancel, ] }; } #[cfg(not(feature = "smoke"))] fn command_handler() -> impl Fn(tauri::ipc::Invoke) -> bool + Send + Sync + 'static { command_list!() } #[cfg(feature = "smoke")] fn command_handler() -> impl Fn(tauri::ipc::Invoke) -> bool + Send + Sync + 'static { command_list!(commands::smoke::smoke_report, commands::smoke::smoke_snapshot) } pub fn run() { let builder = tauri::Builder::default(); // Smoke builds use isolated data and must run next to a developer's open instance. #[cfg(not(feature = "smoke"))] let builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| focus_main(app))); let app = builder .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_opener::init()) .manage(CoreSlot::default()) .manage(LicenseSlot::default()) .register_uri_scheme_protocol("chj-module", protocol::handle) .invoke_handler(command_handler()) .on_window_event(on_window_event) .setup(|app| { bootstrap::start(app.handle()); Ok(()) }) .build(tauri::generate_context!()) .expect("error while building CH-J Server Manager"); app.run(|handle, event| match event { // Closing the last window: keep running while the Core is starting (license accepted, // main window not yet created) and on macOS (same as Electron `window-all-closed`). RunEvent::ExitRequested { code: None, api, .. } => { if handle.state::().try_get().is_none() || cfg!(target_os = "macos") { if handle.state::().0.lock().is_none() || handle.state::().try_get().is_some() { api.prevent_exit(); } } } RunEvent::Exit => { if let Some(core) = handle.state::().try_get() { bootstrap::shutdown(&core); } } #[cfg(target_os = "macos")] RunEvent::Reopen { has_visible_windows, .. } => { if !has_visible_windows { if let Some(core) = handle.state::().try_get() { if handle.get_webview_window(MAIN_LABEL).is_none() { let _ = windows::create_main_window(handle, &core.i18n); } else { focus_main(handle); } } } } _ => {} }); }