From 89b19ea7dda5729a875a3a41d8545e55fa3a77eb Mon Sep 17 00:00:00 2001 From: mofeng-git Date: Fri, 1 May 2026 20:06:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/state.rs | 29 +++- src/web/handlers/config/apply.rs | 136 ++++++++++-------- src/web/handlers/config/atx.rs | 7 +- src/web/handlers/config/audio.rs | 7 +- src/web/handlers/config/hid.rs | 13 +- src/web/handlers/config/msd.rs | 13 +- src/web/handlers/config/rtsp.rs | 32 ++--- src/web/handlers/config/rustdesk.rs | 13 +- src/web/handlers/config/stream.rs | 17 ++- src/web/handlers/config/video.rs | 14 +- src/web/handlers/mod.rs | 175 +++-------------------- src/web/routes.rs | 1 - web/src/App.vue | 3 + web/src/api/index.ts | 10 -- web/src/views/ConsoleView.vue | 213 +++------------------------- web/src/views/SettingsView.vue | 15 +- 16 files changed, 210 insertions(+), 488 deletions(-) diff --git a/src/state.rs b/src/state.rs index b0a4012f..f0935639 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,5 @@ use std::{collections::VecDeque, sync::Arc}; -use tokio::sync::{broadcast, watch, RwLock}; +use tokio::sync::{broadcast, watch, Mutex, RwLock}; use crate::atx::AtxController; use crate::audio::AudioController; @@ -20,6 +20,31 @@ use crate::update::UpdateService; use crate::video::VideoStreamManager; use crate::webrtc::WebRtcStreamer; +#[derive(Clone)] +pub struct ConfigApplyLocks { + pub video: Arc>, + pub stream: Arc>, + pub otg: Arc>, + pub audio: Arc>, + pub atx: Arc>, + pub rustdesk: Arc>, + pub rtsp: Arc>, +} + +impl ConfigApplyLocks { + fn new() -> Self { + Self { + video: Arc::new(Mutex::new(())), + stream: Arc::new(Mutex::new(())), + otg: Arc::new(Mutex::new(())), + audio: Arc::new(Mutex::new(())), + atx: Arc::new(Mutex::new(())), + rustdesk: Arc::new(Mutex::new(())), + rtsp: Arc::new(Mutex::new(())), + } + } +} + /// Shared Axum/App state: video flows through [`VideoStreamManager`]; WebRTC SDP/ICE/sessions on [`WebRtcStreamer`]. pub struct AppState { pub db: DatabasePool, @@ -41,6 +66,7 @@ pub struct AppState { pub update: Arc, pub shutdown_tx: broadcast::Sender<()>, pub revoked_sessions: Arc>>, + pub config_apply_locks: ConfigApplyLocks, data_dir: std::path::PathBuf, } @@ -88,6 +114,7 @@ impl AppState { update, shutdown_tx, revoked_sessions: Arc::new(RwLock::new(VecDeque::new())), + config_apply_locks: ConfigApplyLocks::new(), data_dir, }) } diff --git a/src/web/handlers/config/apply.rs b/src/web/handlers/config/apply.rs index 2dcde606..3e0677ac 100644 --- a/src/web/handlers/config/apply.rs +++ b/src/web/handlers/config/apply.rs @@ -8,6 +8,24 @@ use crate::stream_encoder::encoder_type_to_backend; use crate::video::codec_constraints::{ enforce_constraints_with_stream_manager, StreamCodecConstraints, }; +use tokio::sync::{Mutex, OwnedMutexGuard}; + +#[derive(Debug, Clone, Copy, Default)] +pub struct ConfigApplyOptions { + pub force: bool, +} + +impl ConfigApplyOptions { + pub const fn forced() -> Self { + Self { force: true } + } +} + +pub fn try_apply_lock(lock: &Arc>, domain: &str) -> Result> { + lock.clone().try_lock_owned().map_err(|_| { + AppError::ServiceUnavailable(format!("{domain} configuration is already applying")) + }) +} fn hid_backend_type(config: &HidConfig) -> crate::hid::HidBackendType { match config.backend { @@ -33,8 +51,9 @@ pub async fn apply_video_config( state: &Arc, old_config: &VideoConfig, new_config: &VideoConfig, + options: ConfigApplyOptions, ) -> Result<()> { - if old_config == new_config { + if old_config == new_config && !options.force { tracing::info!("Video config unchanged, skipping reload"); return Ok(()); } @@ -73,10 +92,11 @@ pub async fn apply_stream_config( state: &Arc, old_config: &StreamConfig, new_config: &StreamConfig, + options: ConfigApplyOptions, ) -> Result<()> { tracing::info!("Applying stream config changes..."); - if old_config.encoder != new_config.encoder { + if options.force || old_config.encoder != new_config.encoder { let encoder_backend = encoder_type_to_backend(new_config.encoder.clone()); tracing::info!( "Updating encoder backend to: {:?} (from config: {:?})", @@ -86,12 +106,11 @@ pub async fn apply_stream_config( state.webrtc.update_encoder_backend(encoder_backend).await; } - if old_config.bitrate_preset != new_config.bitrate_preset { + if options.force || old_config.bitrate_preset != new_config.bitrate_preset { state .stream_manager .set_bitrate_preset(new_config.bitrate_preset) - .await - .ok(); // Ignore error if no active stream + .await?; } let ice_changed = old_config.stun_server != new_config.stun_server @@ -99,7 +118,7 @@ pub async fn apply_stream_config( || old_config.turn_username != new_config.turn_username || old_config.turn_password != new_config.turn_password; - if ice_changed { + if options.force || ice_changed { tracing::info!( "Updating ICE config: STUN={:?}, TURN={:?}", new_config.stun_server, @@ -128,6 +147,7 @@ pub async fn apply_hid_config( state: &Arc, old_config: &HidConfig, new_config: &HidConfig, + options: ConfigApplyOptions, ) -> Result<()> { let current_msd_enabled = state.config.get().msd.enabled; new_config.validate_otg_endpoint_budget(current_msd_enabled)?; @@ -149,6 +169,7 @@ pub async fn apply_hid_config( && !hid_functions_changed && !keyboard_leds_changed && !endpoint_budget_changed + && !options.force { tracing::info!("HID config unchanged, skipping reload"); return Ok(()); @@ -190,6 +211,7 @@ pub async fn apply_msd_config( state: &Arc, old_config: &MsdConfig, new_config: &MsdConfig, + options: ConfigApplyOptions, ) -> Result<()> { state .config @@ -222,7 +244,7 @@ pub async fn apply_msd_config( tracing::warn!("Failed to create MSD ventoy directory: {}", e); } - let needs_reload = old_msd_enabled != new_msd_enabled || msd_dir_changed; + let needs_reload = options.force || old_msd_enabled != new_msd_enabled || msd_dir_changed; if !needs_reload { tracing::info!( "MSD enabled state unchanged ({}) and directory unchanged, no reload needed", @@ -272,7 +294,9 @@ pub async fn apply_msd_config( } let current_config = state.config.get(); - if current_config.hid.backend == HidBackend::Otg && old_msd_enabled != new_msd_enabled { + if current_config.hid.backend == HidBackend::Otg + && (options.force || old_msd_enabled != new_msd_enabled) + { state .hid .reload(crate::hid::HidBackendType::Otg) @@ -306,12 +330,11 @@ pub async fn apply_atx_config( tracing::info!("ATX enabled in config, initializing..."); let atx = crate::atx::AtxController::new(controller_config); - if let Err(e) = atx.init().await { - tracing::warn!("ATX initialization failed: {}", e); - } else { - *state.atx.write().await = Some(atx); - tracing::info!("ATX controller initialized successfully"); - } + atx.init() + .await + .map_err(|e| AppError::Config(format!("ATX initialization failed: {}", e)))?; + *state.atx.write().await = Some(atx); + tracing::info!("ATX controller initialized successfully"); } } @@ -331,25 +354,18 @@ pub async fn apply_audio_config( quality: new_config.quality.parse::()?, }; - if let Err(e) = state.audio.update_config(audio_config).await { - tracing::error!("Audio config update failed: {}", e); - } else { - tracing::info!( - "Audio config applied: enabled={}, device={}", - new_config.enabled, - new_config.device - ); - } + state.audio.update_config(audio_config).await?; + tracing::info!( + "Audio config applied: enabled={}, device={}", + new_config.enabled, + new_config.device + ); - if let Err(e) = state + state .stream_manager .set_webrtc_audio_enabled(new_config.enabled) - .await - { - tracing::warn!("Failed to update WebRTC audio state: {}", e); - } else { - tracing::info!("WebRTC audio enabled: {}", new_config.enabled); - } + .await?; + tracing::info!("WebRTC audio enabled: {}", new_config.enabled); if new_config.enabled { state.stream_manager.reconnect_webrtc_audio_sources().await; @@ -370,6 +386,7 @@ pub async fn apply_rustdesk_config( state: &Arc, old_config: &crate::rustdesk::config::RustDeskConfig, new_config: &crate::rustdesk::config::RustDeskConfig, + options: ConfigApplyOptions, ) -> Result<()> { tracing::info!("Applying RustDesk config changes..."); @@ -378,16 +395,18 @@ pub async fn apply_rustdesk_config( if old_config.enabled && !new_config.enabled { if let Some(ref service) = *rustdesk_guard { - if let Err(e) = service.stop().await { - tracing::error!("Failed to stop RustDesk service: {}", e); - } + service + .stop() + .await + .map_err(|e| AppError::Config(format!("Failed to stop RustDesk service: {}", e)))?; tracing::info!("RustDesk service stopped"); } *rustdesk_guard = None; } if new_config.enabled { - let need_restart = old_config.rendezvous_server != new_config.rendezvous_server + let need_restart = options.force + || old_config.rendezvous_server != new_config.rendezvous_server || old_config.device_id != new_config.device_id || old_config.device_password != new_config.device_password; @@ -399,24 +418,22 @@ pub async fn apply_rustdesk_config( state.hid.clone(), state.audio.clone(), ); - if let Err(e) = service.start().await { - tracing::error!("Failed to start RustDesk service: {}", e); - } else { - tracing::info!("RustDesk service started with ID: {}", new_config.device_id); - credentials_to_save = service.save_credentials(); - } + service.start().await.map_err(|e| { + AppError::Config(format!("Failed to start RustDesk service: {}", e)) + })?; + tracing::info!("RustDesk service started with ID: {}", new_config.device_id); + credentials_to_save = service.save_credentials(); *rustdesk_guard = Some(std::sync::Arc::new(service)); } else if need_restart { if let Some(ref service) = *rustdesk_guard { - if let Err(e) = service.restart(new_config.clone()).await { - tracing::error!("Failed to restart RustDesk service: {}", e); - } else { - tracing::info!( - "RustDesk service restarted with ID: {}", - new_config.device_id - ); - credentials_to_save = service.save_credentials(); - } + service.restart(new_config.clone()).await.map_err(|e| { + AppError::Config(format!("Failed to restart RustDesk service: {}", e)) + })?; + tracing::info!( + "RustDesk service restarted with ID: {}", + new_config.device_id + ); + credentials_to_save = service.save_credentials(); } } } @@ -424,7 +441,7 @@ pub async fn apply_rustdesk_config( drop(rustdesk_guard); if let Some(updated_config) = credentials_to_save { tracing::info!("Saving RustDesk credentials to config store..."); - if let Err(e) = state + state .config .update(|cfg| { cfg.rustdesk.public_key = updated_config.public_key.clone(); @@ -433,12 +450,8 @@ pub async fn apply_rustdesk_config( cfg.rustdesk.signing_private_key = updated_config.signing_private_key.clone(); cfg.rustdesk.uuid = updated_config.uuid.clone(); }) - .await - { - tracing::warn!("Failed to save RustDesk credentials: {}", e); - } else { - tracing::info!("RustDesk credentials saved successfully"); - } + .await?; + tracing::info!("RustDesk credentials saved successfully"); } if let Some(message) = enforce_stream_codec_constraints(state).await? { @@ -452,6 +465,7 @@ pub async fn apply_rtsp_config( state: &Arc, old_config: &RtspConfig, new_config: &RtspConfig, + options: ConfigApplyOptions, ) -> Result<()> { tracing::info!("Applying RTSP config changes..."); @@ -459,15 +473,17 @@ pub async fn apply_rtsp_config( if old_config.enabled && !new_config.enabled { if let Some(ref service) = *rtsp_guard { - if let Err(e) = service.stop().await { - tracing::error!("Failed to stop RTSP service: {}", e); - } + service + .stop() + .await + .map_err(|e| AppError::Config(format!("Failed to stop RTSP service: {}", e)))?; } *rtsp_guard = None; } if new_config.enabled { - let need_restart = old_config.bind != new_config.bind + let need_restart = options.force + || old_config.bind != new_config.bind || old_config.port != new_config.port || old_config.path != new_config.path || old_config.codec != new_config.codec diff --git a/src/web/handlers/config/atx.rs b/src/web/handlers/config/atx.rs index e61c8de4..1e60270f 100644 --- a/src/web/handlers/config/atx.rs +++ b/src/web/handlers/config/atx.rs @@ -6,7 +6,7 @@ use crate::config::{AtxConfig, HidBackend, HidConfig}; use crate::error::{AppError, Result}; use crate::state::AppState; -use super::apply::apply_atx_config; +use super::apply::{apply_atx_config, try_apply_lock}; use super::types::AtxConfigUpdate; pub async fn get_atx_config(State(state): State>) -> Json { @@ -22,6 +22,7 @@ pub async fn update_atx_config( req.validate_with_current(&old_atx_config)?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.atx, "atx")?; let mut merged_atx_config = old_atx_config.clone(); req.apply_to(&mut merged_atx_config); validate_serial_device_conflict(&merged_atx_config, ¤t_config.hid)?; @@ -35,9 +36,7 @@ pub async fn update_atx_config( let new_atx_config = state.config.get().atx.clone(); - if let Err(e) = apply_atx_config(&state, &old_atx_config, &new_atx_config).await { - tracing::error!("Failed to apply ATX config: {}", e); - } + apply_atx_config(&state, &old_atx_config, &new_atx_config).await?; Ok(Json(new_atx_config)) } diff --git a/src/web/handlers/config/audio.rs b/src/web/handlers/config/audio.rs index c2e325f5..5e92d340 100644 --- a/src/web/handlers/config/audio.rs +++ b/src/web/handlers/config/audio.rs @@ -5,7 +5,7 @@ use crate::config::AudioConfig; use crate::error::Result; use crate::state::AppState; -use super::apply::apply_audio_config; +use super::apply::{apply_audio_config, try_apply_lock}; use super::types::AudioConfigUpdate; pub async fn get_audio_config(State(state): State>) -> Json { @@ -18,6 +18,7 @@ pub async fn update_audio_config( ) -> Result> { req.validate()?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.audio, "audio")?; let old_audio_config = state.config.get().audio.clone(); state @@ -29,9 +30,7 @@ pub async fn update_audio_config( let new_audio_config = state.config.get().audio.clone(); - if let Err(e) = apply_audio_config(&state, &old_audio_config, &new_audio_config).await { - tracing::error!("Failed to apply audio config: {}", e); - } + apply_audio_config(&state, &old_audio_config, &new_audio_config).await?; Ok(Json(new_audio_config)) } diff --git a/src/web/handlers/config/hid.rs b/src/web/handlers/config/hid.rs index c06e532c..17514448 100644 --- a/src/web/handlers/config/hid.rs +++ b/src/web/handlers/config/hid.rs @@ -5,7 +5,7 @@ use crate::config::HidConfig; use crate::error::Result; use crate::state::AppState; -use super::apply::apply_hid_config; +use super::apply::{apply_hid_config, try_apply_lock, ConfigApplyOptions}; use super::types::HidConfigUpdate; pub async fn get_hid_config(State(state): State>) -> Json { @@ -18,6 +18,7 @@ pub async fn update_hid_config( ) -> Result> { req.validate()?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.otg, "otg")?; let old_hid_config = state.config.get().hid.clone(); state @@ -29,9 +30,13 @@ pub async fn update_hid_config( let new_hid_config = state.config.get().hid.clone(); - if let Err(e) = apply_hid_config(&state, &old_hid_config, &new_hid_config).await { - tracing::error!("Failed to apply HID config: {}", e); - } + apply_hid_config( + &state, + &old_hid_config, + &new_hid_config, + ConfigApplyOptions::forced(), + ) + .await?; Ok(Json(new_hid_config)) } diff --git a/src/web/handlers/config/msd.rs b/src/web/handlers/config/msd.rs index a8fcaa23..cfa38ba3 100644 --- a/src/web/handlers/config/msd.rs +++ b/src/web/handlers/config/msd.rs @@ -5,7 +5,7 @@ use crate::config::MsdConfig; use crate::error::Result; use crate::state::AppState; -use super::apply::apply_msd_config; +use super::apply::{apply_msd_config, try_apply_lock, ConfigApplyOptions}; use super::types::MsdConfigUpdate; pub async fn get_msd_config(State(state): State>) -> Json { @@ -18,6 +18,7 @@ pub async fn update_msd_config( ) -> Result> { req.validate()?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.otg, "otg")?; let old_msd_config = state.config.get().msd.clone(); state @@ -29,9 +30,13 @@ pub async fn update_msd_config( let new_msd_config = state.config.get().msd.clone(); - if let Err(e) = apply_msd_config(&state, &old_msd_config, &new_msd_config).await { - tracing::error!("Failed to apply MSD config: {}", e); - } + apply_msd_config( + &state, + &old_msd_config, + &new_msd_config, + ConfigApplyOptions::forced(), + ) + .await?; Ok(Json(new_msd_config)) } diff --git a/src/web/handlers/config/rtsp.rs b/src/web/handlers/config/rtsp.rs index 520e856d..0a70d0bb 100644 --- a/src/web/handlers/config/rtsp.rs +++ b/src/web/handlers/config/rtsp.rs @@ -1,10 +1,10 @@ use axum::{extract::State, Json}; use std::sync::Arc; -use crate::error::{AppError, Result}; +use crate::error::Result; use crate::state::AppState; -use super::apply::apply_rtsp_config; +use super::apply::{apply_rtsp_config, try_apply_lock, ConfigApplyOptions}; use super::types::{RtspConfigResponse, RtspConfigUpdate, RtspStatusResponse}; pub async fn get_rtsp_config(State(state): State>) -> Json { @@ -32,6 +32,7 @@ pub async fn update_rtsp_config( ) -> Result> { req.validate()?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?; let old_config = state.config.get().rtsp.clone(); state @@ -42,26 +43,13 @@ pub async fn update_rtsp_config( .await?; let new_config = state.config.get().rtsp.clone(); - if let Err(err) = apply_rtsp_config(&state, &old_config, &new_config).await { - tracing::error!("Failed to apply RTSP config: {}", err); - if let Err(rollback_err) = state - .config - .update(|config| { - config.rtsp = old_config.clone(); - }) - .await - { - tracing::error!( - "Failed to rollback RTSP config after apply failure: {}", - rollback_err - ); - return Err(AppError::ServiceUnavailable(format!( - "RTSP apply failed: {}; rollback failed: {}", - err, rollback_err - ))); - } - return Err(err); - } + apply_rtsp_config( + &state, + &old_config, + &new_config, + ConfigApplyOptions::forced(), + ) + .await?; Ok(Json(RtspConfigResponse::from(&new_config))) } diff --git a/src/web/handlers/config/rustdesk.rs b/src/web/handlers/config/rustdesk.rs index 12e47aac..a901b47e 100644 --- a/src/web/handlers/config/rustdesk.rs +++ b/src/web/handlers/config/rustdesk.rs @@ -5,7 +5,7 @@ use crate::error::Result; use crate::rustdesk::config::RustDeskConfig; use crate::state::AppState; -use super::apply::apply_rustdesk_config; +use super::apply::{apply_rustdesk_config, try_apply_lock, ConfigApplyOptions}; use super::types::RustDeskConfigUpdate; #[derive(Debug, serde::Serialize)] @@ -75,6 +75,7 @@ pub async fn update_rustdesk_config( ) -> Result> { req.validate()?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?; let old_config = state.config.get().rustdesk.clone(); state @@ -86,9 +87,13 @@ pub async fn update_rustdesk_config( let new_config = state.config.get().rustdesk.clone(); - if let Err(e) = apply_rustdesk_config(&state, &old_config, &new_config).await { - tracing::error!("Failed to apply RustDesk config: {}", e); - } + apply_rustdesk_config( + &state, + &old_config, + &new_config, + ConfigApplyOptions::forced(), + ) + .await?; let constraints = state.stream_manager.codec_constraints().await; if constraints.rustdesk_enabled || constraints.rtsp_enabled { diff --git a/src/web/handlers/config/stream.rs b/src/web/handlers/config/stream.rs index 4950996b..5b43f006 100644 --- a/src/web/handlers/config/stream.rs +++ b/src/web/handlers/config/stream.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::error::Result; use crate::state::AppState; -use super::apply::apply_stream_config; +use super::apply::{apply_stream_config, try_apply_lock, ConfigApplyOptions}; use super::types::{StreamConfigResponse, StreamConfigUpdate}; pub async fn get_stream_config(State(state): State>) -> Json { @@ -18,6 +18,7 @@ pub async fn update_stream_config( ) -> Result> { req.validate()?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.stream, "stream")?; let old_stream_config = state.config.get().stream.clone(); state @@ -29,13 +30,15 @@ pub async fn update_stream_config( let new_stream_config = state.config.get().stream.clone(); - if let Err(e) = apply_stream_config(&state, &old_stream_config, &new_stream_config).await { - tracing::error!("Failed to apply stream config: {}", e); - } + apply_stream_config( + &state, + &old_stream_config, + &new_stream_config, + ConfigApplyOptions::forced(), + ) + .await?; - if let Err(e) = super::apply::enforce_stream_codec_constraints(&state).await { - tracing::error!("Failed to enforce stream codec constraints: {}", e); - } + super::apply::enforce_stream_codec_constraints(&state).await?; Ok(Json(StreamConfigResponse::from(&new_stream_config))) } diff --git a/src/web/handlers/config/video.rs b/src/web/handlers/config/video.rs index de80837a..865da9d5 100644 --- a/src/web/handlers/config/video.rs +++ b/src/web/handlers/config/video.rs @@ -5,7 +5,7 @@ use crate::config::VideoConfig; use crate::error::Result; use crate::state::AppState; -use super::apply::apply_video_config; +use super::apply::{apply_video_config, try_apply_lock, ConfigApplyOptions}; use super::types::VideoConfigUpdate; pub async fn get_video_config(State(state): State>) -> Json { @@ -18,6 +18,7 @@ pub async fn update_video_config( ) -> Result> { req.validate()?; + let _apply_guard = try_apply_lock(&state.config_apply_locks.video, "video")?; let old_video_config = state.config.get().video.clone(); state @@ -29,10 +30,13 @@ pub async fn update_video_config( let new_video_config = state.config.get().video.clone(); - if let Err(e) = apply_video_config(&state, &old_video_config, &new_video_config).await { - tracing::error!("Failed to apply video config: {}", e); - // 根据用户选择,仅记录错误,不回滚 - } + apply_video_config( + &state, + &old_video_config, + &new_video_config, + ConfigApplyOptions::forced(), + ) + .await?; Ok(Json(new_video_config)) } diff --git a/src/web/handlers/mod.rs b/src/web/handlers/mod.rs index d7840b80..2a57dd21 100644 --- a/src/web/handlers/mod.rs +++ b/src/web/handlers/mod.rs @@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tracing::{info, warn}; +use self::config::apply::ConfigApplyOptions; use crate::auth::{Session, SESSION_COOKIE}; -use crate::config::{AppConfig, StreamMode}; +use crate::config::StreamMode; use crate::error::{AppError, Result}; use crate::state::AppState; use crate::update::{UpdateChannel, UpdateOverviewResponse, UpdateStatusResponse, UpgradeRequest}; @@ -739,8 +740,13 @@ pub async fn setup_init( // Start RustDesk if enabled if new_config.rustdesk.enabled { let empty_config = crate::rustdesk::config::RustDeskConfig::default(); - if let Err(e) = - config::apply::apply_rustdesk_config(&state, &empty_config, &new_config.rustdesk).await + if let Err(e) = config::apply::apply_rustdesk_config( + &state, + &empty_config, + &new_config.rustdesk, + ConfigApplyOptions::default(), + ) + .await { tracing::warn!("Failed to start RustDesk during setup: {}", e); } else { @@ -751,8 +757,13 @@ pub async fn setup_init( // Start RTSP if enabled if new_config.rtsp.enabled { let empty_config = crate::config::RtspConfig::default(); - if let Err(e) = - config::apply::apply_rtsp_config(&state, &empty_config, &new_config.rtsp).await + if let Err(e) = config::apply::apply_rtsp_config( + &state, + &empty_config, + &new_config.rtsp, + ConfigApplyOptions::default(), + ) + .await { tracing::warn!("Failed to start RTSP during setup: {}", e); } else { @@ -792,160 +803,6 @@ pub async fn setup_init( })) } -#[derive(Deserialize)] -pub struct UpdateConfigRequest { - #[serde(flatten)] - pub updates: serde_json::Value, -} - -pub async fn update_config( - State(state): State>, - Json(req): Json, -) -> Result> { - // Keep old config for rollback - let old_config = state.config.get(); - - tracing::info!("Received config update request"); - - // Validate and merge config first (outside the update closure) - let config_json = serde_json::to_value(&old_config) - .map_err(|e| AppError::Internal(format!("Failed to serialize config: {}", e)))?; - - let merged = merge_json(config_json, req.updates.clone()) - .map_err(|_| AppError::Internal("Failed to merge config".to_string()))?; - - let new_config: AppConfig = serde_json::from_value(merged) - .map_err(|e| AppError::BadRequest(format!("Invalid config format: {}", e)))?; - - let new_config = new_config; - new_config - .hid - .validate_otg_endpoint_budget(new_config.msd.enabled)?; - - // Apply the validated config - state.config.set(new_config.clone()).await?; - - tracing::info!("Config updated successfully"); - - // Detect which config sections were sent in the request - let has_video = req.updates.get("video").is_some(); - let has_stream = req.updates.get("stream").is_some(); - let has_hid = req.updates.get("hid").is_some(); - let has_msd = req.updates.get("msd").is_some(); - let has_atx = req.updates.get("atx").is_some(); - let has_audio = req.updates.get("audio").is_some(); - - tracing::info!( - "Config sections sent: video={}, stream={}, hid={}, msd={}, atx={}, audio={}", - has_video, - has_stream, - has_hid, - has_msd, - has_atx, - has_audio - ); - - // Get new config for device reloading - let new_config = state.config.get(); - - if has_video { - if let Err(e) = - config::apply::apply_video_config(&state, &old_config.video, &new_config.video).await - { - tracing::error!("Failed to apply video config: {}", e); - state.config.set((*old_config).clone()).await?; - return Ok(Json(LoginResponse { - success: false, - message: Some(format!("Video configuration invalid: {}", e)), - })); - } - } - - if has_stream { - if let Err(e) = - config::apply::apply_stream_config(&state, &old_config.stream, &new_config.stream).await - { - tracing::error!("Failed to apply stream config: {}", e); - state.config.set((*old_config).clone()).await?; - return Ok(Json(LoginResponse { - success: false, - message: Some(format!("Stream configuration invalid: {}", e)), - })); - } - } - - if has_hid { - if let Err(e) = - config::apply::apply_hid_config(&state, &old_config.hid, &new_config.hid).await - { - tracing::error!("HID reload failed: {}", e); - state.config.set((*old_config).clone()).await?; - return Ok(Json(LoginResponse { - success: false, - message: Some(format!("HID configuration invalid: {}", e)), - })); - } - } - - if has_audio { - if let Err(e) = - config::apply::apply_audio_config(&state, &old_config.audio, &new_config.audio).await - { - tracing::warn!("Audio config update failed: {}", e); - } - } - - if has_msd { - if let Err(e) = - config::apply::apply_msd_config(&state, &old_config.msd, &new_config.msd).await - { - tracing::error!("MSD initialization failed: {}", e); - state.config.set((*old_config).clone()).await?; - return Ok(Json(LoginResponse { - success: false, - message: Some(format!("MSD initialization failed: {}", e)), - })); - } - } - - if has_atx { - if let Err(e) = - config::apply::apply_atx_config(&state, &old_config.atx, &new_config.atx).await - { - tracing::error!("ATX configuration invalid: {}", e); - state.config.set((*old_config).clone()).await?; - return Ok(Json(LoginResponse { - success: false, - message: Some(format!("ATX configuration invalid: {}", e)), - })); - } - } - - Ok(Json(LoginResponse { - success: true, - message: Some("Configuration updated".to_string()), - })) -} - -fn merge_json( - base: serde_json::Value, - updates: serde_json::Value, -) -> std::result::Result { - match (base, updates) { - (serde_json::Value::Object(mut base), serde_json::Value::Object(updates)) => { - for (key, value) in updates { - if let Some(base_value) = base.get(&key).cloned() { - base.insert(key, merge_json(base_value, value)?); - } else { - base.insert(key, value); - } - } - Ok(serde_json::Value::Object(base)) - } - (_, updates) => Ok(updates), - } -} - #[derive(Serialize)] pub struct DeviceList { pub video: Vec, diff --git a/src/web/routes.rs b/src/web/routes.rs index 2260d249..7d6ac2ff 100644 --- a/src/web/routes.rs +++ b/src/web/routes.rs @@ -78,7 +78,6 @@ pub fn create_router(state: Arc) -> Router { .route("/ws/audio", any(audio_ws_handler)) // Configuration management (domain-separated endpoints) .route("/config", get(handlers::config::get_all_config)) - .route("/config", post(handlers::update_config)) .route("/config/video", get(handlers::config::get_video_config)) .route( "/config/video", diff --git a/web/src/App.vue b/web/src/App.vue index d31f7338..c150d922 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,8 +1,10 @@