mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 19:51:58 +08:00
refactor: 删除部分多余的代码和注释
This commit is contained in:
@@ -1,13 +1,10 @@
|
||||
//! 配置热重载逻辑
|
||||
//!
|
||||
//! 从 handlers.rs 中抽取的配置应用函数,负责将配置变更应用到各个子系统。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::*;
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::rtsp::RtspService;
|
||||
use crate::state::AppState;
|
||||
use crate::stream_encoder::encoder_type_to_backend;
|
||||
use crate::video::codec_constraints::{
|
||||
enforce_constraints_with_stream_manager, StreamCodecConstraints,
|
||||
};
|
||||
@@ -32,13 +29,11 @@ async fn reconcile_otg_from_store(state: &Arc<AppState>) -> Result<()> {
|
||||
.map_err(|e| AppError::Config(format!("OTG reconcile failed: {}", e)))
|
||||
}
|
||||
|
||||
/// 应用 Video 配置变更
|
||||
pub async fn apply_video_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &VideoConfig,
|
||||
new_config: &VideoConfig,
|
||||
) -> Result<()> {
|
||||
// 检查配置是否实际变更
|
||||
if old_config == new_config {
|
||||
tracing::info!("Video config unchanged, skipping reload");
|
||||
return Ok(());
|
||||
@@ -74,7 +69,6 @@ pub async fn apply_video_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 应用 Stream 配置变更
|
||||
pub async fn apply_stream_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &StreamConfig,
|
||||
@@ -82,32 +76,24 @@ pub async fn apply_stream_config(
|
||||
) -> Result<()> {
|
||||
tracing::info!("Applying stream config changes...");
|
||||
|
||||
// 更新编码器后端
|
||||
if old_config.encoder != new_config.encoder {
|
||||
let encoder_backend = new_config.encoder.to_backend();
|
||||
let encoder_backend = encoder_type_to_backend(new_config.encoder.clone());
|
||||
tracing::info!(
|
||||
"Updating encoder backend to: {:?} (from config: {:?})",
|
||||
encoder_backend,
|
||||
new_config.encoder
|
||||
);
|
||||
state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.update_encoder_backend(encoder_backend)
|
||||
.await;
|
||||
state.webrtc.update_encoder_backend(encoder_backend).await;
|
||||
}
|
||||
|
||||
// 更新码率
|
||||
if old_config.bitrate_preset != new_config.bitrate_preset {
|
||||
state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.set_bitrate_preset(new_config.bitrate_preset)
|
||||
.await
|
||||
.ok(); // Ignore error if no active stream
|
||||
}
|
||||
|
||||
// 更新 ICE 配置 (STUN/TURN)
|
||||
let ice_changed = old_config.stun_server != new_config.stun_server
|
||||
|| old_config.turn_server != new_config.turn_server
|
||||
|| old_config.turn_username != new_config.turn_username
|
||||
@@ -120,8 +106,7 @@ pub async fn apply_stream_config(
|
||||
new_config.turn_server
|
||||
);
|
||||
state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.webrtc
|
||||
.update_ice_config(
|
||||
new_config.stun_server.clone(),
|
||||
new_config.turn_server.clone(),
|
||||
@@ -139,7 +124,6 @@ pub async fn apply_stream_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 应用 HID 配置变更
|
||||
pub async fn apply_hid_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &HidConfig,
|
||||
@@ -202,7 +186,6 @@ pub async fn apply_hid_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 应用 MSD 配置变更
|
||||
pub async fn apply_msd_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &MsdConfig,
|
||||
@@ -218,7 +201,6 @@ pub async fn apply_msd_config(
|
||||
tracing::debug!("Old MSD config: {:?}", old_config);
|
||||
tracing::debug!("New MSD config: {:?}", new_config);
|
||||
|
||||
// Check if MSD enabled state changed
|
||||
let old_msd_enabled = old_config.enabled;
|
||||
let new_msd_enabled = new_config.enabled;
|
||||
let msd_dir_changed = old_config.msd_dir != new_config.msd_dir;
|
||||
@@ -232,7 +214,6 @@ pub async fn apply_msd_config(
|
||||
tracing::info!("MSD directory changed: {}", new_config.msd_dir);
|
||||
}
|
||||
|
||||
// Ensure MSD directories exist (msd/images, msd/ventoy)
|
||||
let msd_dir = new_config.msd_dir_path();
|
||||
if let Err(e) = std::fs::create_dir_all(msd_dir.join("images")) {
|
||||
tracing::warn!("Failed to create MSD images directory: {}", e);
|
||||
@@ -255,7 +236,6 @@ pub async fn apply_msd_config(
|
||||
|
||||
reconcile_otg_from_store(state).await?;
|
||||
|
||||
// Shutdown existing controller if present
|
||||
let mut msd_guard = state.msd.write().await;
|
||||
if let Some(msd) = msd_guard.as_mut() {
|
||||
if let Err(e) = msd.shutdown().await {
|
||||
@@ -271,15 +251,12 @@ pub async fn apply_msd_config(
|
||||
.await
|
||||
.map_err(|e| AppError::Config(format!("MSD initialization failed: {}", e)))?;
|
||||
|
||||
// Set event bus
|
||||
let events = state.events.clone();
|
||||
msd.set_event_bus(events).await;
|
||||
|
||||
// Store the initialized controller
|
||||
*state.msd.write().await = Some(msd);
|
||||
tracing::info!("MSD initialized successfully");
|
||||
} else {
|
||||
// MSD disabled - shutdown
|
||||
tracing::info!("MSD disabled in config, shutting down...");
|
||||
|
||||
let mut msd_guard = state.msd.write().await;
|
||||
@@ -306,7 +283,6 @@ pub async fn apply_msd_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 应用 ATX 配置变更
|
||||
pub async fn apply_atx_config(
|
||||
state: &Arc<AppState>,
|
||||
_old_config: &AtxConfig,
|
||||
@@ -314,10 +290,8 @@ pub async fn apply_atx_config(
|
||||
) -> Result<()> {
|
||||
tracing::info!("Applying ATX config changes...");
|
||||
|
||||
// Convert AtxConfig to AtxControllerConfig
|
||||
let controller_config = new_config.to_controller_config();
|
||||
|
||||
// Reload the ATX controller with new configuration
|
||||
let atx_guard = state.atx.read().await;
|
||||
if let Some(atx) = atx_guard.as_ref() {
|
||||
if let Err(e) = atx.reload(controller_config).await {
|
||||
@@ -326,7 +300,6 @@ pub async fn apply_atx_config(
|
||||
}
|
||||
tracing::info!("ATX controller reloaded successfully");
|
||||
} else {
|
||||
// ATX controller not initialized, create a new one if enabled
|
||||
drop(atx_guard);
|
||||
|
||||
if new_config.enabled {
|
||||
@@ -345,7 +318,6 @@ pub async fn apply_atx_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 应用 Audio 配置变更
|
||||
pub async fn apply_audio_config(
|
||||
state: &Arc<AppState>,
|
||||
_old_config: &AudioConfig,
|
||||
@@ -353,17 +325,14 @@ pub async fn apply_audio_config(
|
||||
) -> Result<()> {
|
||||
tracing::info!("Applying audio config changes...");
|
||||
|
||||
// Create audio controller config from new config
|
||||
let audio_config = crate::audio::AudioControllerConfig {
|
||||
enabled: new_config.enabled,
|
||||
device: new_config.device.clone(),
|
||||
quality: crate::audio::AudioQuality::from_str(&new_config.quality),
|
||||
quality: new_config.quality.parse::<crate::audio::AudioQuality>()?,
|
||||
};
|
||||
|
||||
// Update audio controller
|
||||
if let Err(e) = state.audio.update_config(audio_config).await {
|
||||
tracing::error!("Audio config update failed: {}", e);
|
||||
// Don't fail - audio errors are not critical
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Audio config applied: enabled={}, device={}",
|
||||
@@ -372,7 +341,6 @@ pub async fn apply_audio_config(
|
||||
);
|
||||
}
|
||||
|
||||
// Also update WebRTC audio enabled state
|
||||
if let Err(e) = state
|
||||
.stream_manager
|
||||
.set_webrtc_audio_enabled(new_config.enabled)
|
||||
@@ -383,7 +351,6 @@ pub async fn apply_audio_config(
|
||||
tracing::info!("WebRTC audio enabled: {}", new_config.enabled);
|
||||
}
|
||||
|
||||
// Reconnect audio sources for existing WebRTC sessions
|
||||
if new_config.enabled {
|
||||
state.stream_manager.reconnect_webrtc_audio_sources().await;
|
||||
}
|
||||
@@ -391,7 +358,6 @@ pub async fn apply_audio_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply stream codec constraints derived from global config.
|
||||
pub async fn enforce_stream_codec_constraints(state: &Arc<AppState>) -> Result<Option<String>> {
|
||||
let config = state.config.get();
|
||||
let constraints = StreamCodecConstraints::from_config(&config);
|
||||
@@ -400,7 +366,6 @@ pub async fn enforce_stream_codec_constraints(state: &Arc<AppState>) -> Result<O
|
||||
Ok(enforcement.message)
|
||||
}
|
||||
|
||||
/// 应用 RustDesk 配置变更
|
||||
pub async fn apply_rustdesk_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &crate::rustdesk::config::RustDeskConfig,
|
||||
@@ -411,9 +376,7 @@ pub async fn apply_rustdesk_config(
|
||||
let mut rustdesk_guard = state.rustdesk.write().await;
|
||||
let mut credentials_to_save = None;
|
||||
|
||||
// Check if service needs to be stopped
|
||||
if old_config.enabled && !new_config.enabled {
|
||||
// Disable service
|
||||
if let Some(ref service) = *rustdesk_guard {
|
||||
if let Err(e) = service.stop().await {
|
||||
tracing::error!("Failed to stop RustDesk service: {}", e);
|
||||
@@ -423,14 +386,12 @@ pub async fn apply_rustdesk_config(
|
||||
*rustdesk_guard = None;
|
||||
}
|
||||
|
||||
// Check if service needs to be started or restarted
|
||||
if new_config.enabled {
|
||||
let need_restart = old_config.rendezvous_server != new_config.rendezvous_server
|
||||
|| old_config.device_id != new_config.device_id
|
||||
|| old_config.device_password != new_config.device_password;
|
||||
|
||||
if rustdesk_guard.is_none() {
|
||||
// Create new service
|
||||
tracing::info!("Initializing RustDesk service...");
|
||||
let service = crate::rustdesk::RustDeskService::new(
|
||||
new_config.clone(),
|
||||
@@ -442,12 +403,10 @@ pub async fn apply_rustdesk_config(
|
||||
tracing::error!("Failed to start RustDesk service: {}", e);
|
||||
} else {
|
||||
tracing::info!("RustDesk service started with ID: {}", new_config.device_id);
|
||||
// Save generated keypair and UUID to config
|
||||
credentials_to_save = service.save_credentials();
|
||||
}
|
||||
*rustdesk_guard = Some(std::sync::Arc::new(service));
|
||||
} else if need_restart {
|
||||
// Restart existing service with new config
|
||||
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);
|
||||
@@ -456,14 +415,12 @@ pub async fn apply_rustdesk_config(
|
||||
"RustDesk service restarted with ID: {}",
|
||||
new_config.device_id
|
||||
);
|
||||
// Save generated keypair and UUID to config
|
||||
credentials_to_save = service.save_credentials();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save credentials to persistent config store (outside the lock)
|
||||
drop(rustdesk_guard);
|
||||
if let Some(updated_config) = credentials_to_save {
|
||||
tracing::info!("Saving RustDesk credentials to config store...");
|
||||
@@ -491,7 +448,6 @@ pub async fn apply_rustdesk_config(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 应用 RTSP 配置变更
|
||||
pub async fn apply_rtsp_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &RtspConfig,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! ATX configuration handlers
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -11,29 +9,23 @@ use crate::state::AppState;
|
||||
use super::apply::apply_atx_config;
|
||||
use super::types::AtxConfigUpdate;
|
||||
|
||||
/// Get ATX configuration
|
||||
pub async fn get_atx_config(State(state): State<Arc<AppState>>) -> Json<AtxConfig> {
|
||||
Json(state.config.get().atx.clone())
|
||||
}
|
||||
|
||||
/// Update ATX configuration
|
||||
pub async fn update_atx_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<AtxConfigUpdate>,
|
||||
) -> Result<Json<AtxConfig>> {
|
||||
// 1. Read current configuration snapshot
|
||||
let current_config = state.config.get();
|
||||
let old_atx_config = current_config.atx.clone();
|
||||
|
||||
// 2. Validate request, including merged effective serial parameter checks
|
||||
req.validate_with_current(&old_atx_config)?;
|
||||
|
||||
// 3. Ensure ATX serial devices do not conflict with HID CH9329 serial device
|
||||
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)?;
|
||||
|
||||
// 4. Persist update into config store
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -41,10 +33,8 @@ pub async fn update_atx_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 5. Load new config
|
||||
let new_atx_config = state.config.get().atx.clone();
|
||||
|
||||
// 6. Apply to subsystem (hot reload)
|
||||
if let Err(e) = apply_atx_config(&state, &old_atx_config, &new_atx_config).await {
|
||||
tracing::error!("Failed to apply ATX config: {}", e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Audio 配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -10,23 +8,18 @@ use crate::state::AppState;
|
||||
use super::apply::apply_audio_config;
|
||||
use super::types::AudioConfigUpdate;
|
||||
|
||||
/// 获取 Audio 配置
|
||||
pub async fn get_audio_config(State(state): State<Arc<AppState>>) -> Json<AudioConfig> {
|
||||
Json(state.config.get().audio.clone())
|
||||
}
|
||||
|
||||
/// 更新 Audio 配置
|
||||
pub async fn update_audio_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<AudioConfigUpdate>,
|
||||
) -> Result<Json<AudioConfig>> {
|
||||
// 1. 验证请求
|
||||
req.validate()?;
|
||||
|
||||
// 2. 获取旧配置
|
||||
let old_audio_config = state.config.get().audio.clone();
|
||||
|
||||
// 3. 应用更新到配置存储
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -34,10 +27,8 @@ pub async fn update_audio_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 4. 获取新配置
|
||||
let new_audio_config = state.config.get().audio.clone();
|
||||
|
||||
// 5. 应用到子系统(热重载)
|
||||
if let Err(e) = apply_audio_config(&state, &old_audio_config, &new_audio_config).await {
|
||||
tracing::error!("Failed to apply audio config: {}", e);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ pub async fn get_auth_config(State(state): State<Arc<AppState>>) -> Json<AuthCon
|
||||
Json(auth)
|
||||
}
|
||||
|
||||
/// Update auth configuration
|
||||
pub async fn update_auth_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(update): Json<AuthConfigUpdate>,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! HID 配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -10,23 +8,18 @@ use crate::state::AppState;
|
||||
use super::apply::apply_hid_config;
|
||||
use super::types::HidConfigUpdate;
|
||||
|
||||
/// 获取 HID 配置
|
||||
pub async fn get_hid_config(State(state): State<Arc<AppState>>) -> Json<HidConfig> {
|
||||
Json(state.config.get().hid.clone())
|
||||
}
|
||||
|
||||
/// 更新 HID 配置
|
||||
pub async fn update_hid_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<HidConfigUpdate>,
|
||||
) -> Result<Json<HidConfig>> {
|
||||
// 1. 验证请求
|
||||
req.validate()?;
|
||||
|
||||
// 2. 获取旧配置
|
||||
let old_hid_config = state.config.get().hid.clone();
|
||||
|
||||
// 3. 应用更新到配置存储
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -34,10 +27,8 @@ pub async fn update_hid_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 4. 获取新配置
|
||||
let new_hid_config = state.config.get().hid.clone();
|
||||
|
||||
// 5. 应用到子系统(热重载)
|
||||
if let Err(e) = apply_hid_config(&state, &old_hid_config, &new_hid_config).await {
|
||||
tracing::error!("Failed to apply HID config: {}", e);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
//! 配置管理 Handler 模块
|
||||
//!
|
||||
//! 提供 RESTful 域分离的配置 API:
|
||||
//! - GET /api/config/video - 获取视频配置
|
||||
//! - PATCH /api/config/video - 更新视频配置
|
||||
//! - GET /api/config/stream - 获取流配置
|
||||
//! - PATCH /api/config/stream - 更新流配置
|
||||
//! - GET /api/config/hid - 获取 HID 配置
|
||||
//! - PATCH /api/config/hid - 更新 HID 配置
|
||||
//! - GET /api/config/msd - 获取 MSD 配置
|
||||
//! - PATCH /api/config/msd - 更新 MSD 配置
|
||||
//! - GET /api/config/atx - 获取 ATX 配置
|
||||
//! - PATCH /api/config/atx - 更新 ATX 配置
|
||||
//! - GET /api/config/audio - 获取音频配置
|
||||
//! - PATCH /api/config/audio - 更新音频配置
|
||||
//! - GET /api/config/rustdesk - 获取 RustDesk 配置
|
||||
//! - PATCH /api/config/rustdesk - 更新 RustDesk 配置
|
||||
|
||||
pub(crate) mod apply;
|
||||
mod types;
|
||||
|
||||
@@ -30,7 +12,6 @@ mod stream;
|
||||
pub(crate) mod video;
|
||||
mod web;
|
||||
|
||||
// 导出 handler 函数
|
||||
pub use atx::{get_atx_config, update_atx_config};
|
||||
pub use audio::{get_audio_config, update_audio_config};
|
||||
pub use auth::{get_auth_config, update_auth_config};
|
||||
@@ -45,7 +26,6 @@ pub use stream::{get_stream_config, update_stream_config};
|
||||
pub use video::{get_video_config, update_video_config};
|
||||
pub use web::{get_web_config, update_web_config};
|
||||
|
||||
// 保留全局配置查询(向后兼容)
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -53,13 +33,10 @@ use crate::config::AppConfig;
|
||||
use crate::state::AppState;
|
||||
|
||||
fn sanitize_config_for_api(config: &mut AppConfig) {
|
||||
// Auth secrets
|
||||
config.auth.totp_secret = None;
|
||||
|
||||
// Stream secrets
|
||||
config.stream.turn_password = None;
|
||||
|
||||
// RustDesk secrets
|
||||
config.rustdesk.device_password.clear();
|
||||
config.rustdesk.relay_key = None;
|
||||
config.rustdesk.public_key = None;
|
||||
@@ -67,14 +44,11 @@ fn sanitize_config_for_api(config: &mut AppConfig) {
|
||||
config.rustdesk.signing_public_key = None;
|
||||
config.rustdesk.signing_private_key = None;
|
||||
|
||||
// RTSP secrets
|
||||
config.rtsp.password = None;
|
||||
}
|
||||
|
||||
/// 获取完整配置
|
||||
pub async fn get_all_config(State(state): State<Arc<AppState>>) -> Json<AppConfig> {
|
||||
let mut config = (*state.config.get()).clone();
|
||||
// 不暴露敏感信息
|
||||
sanitize_config_for_api(&mut config);
|
||||
Json(config)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! MSD 配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -10,23 +8,18 @@ use crate::state::AppState;
|
||||
use super::apply::apply_msd_config;
|
||||
use super::types::MsdConfigUpdate;
|
||||
|
||||
/// 获取 MSD 配置
|
||||
pub async fn get_msd_config(State(state): State<Arc<AppState>>) -> Json<MsdConfig> {
|
||||
Json(state.config.get().msd.clone())
|
||||
}
|
||||
|
||||
/// 更新 MSD 配置
|
||||
pub async fn update_msd_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<MsdConfigUpdate>,
|
||||
) -> Result<Json<MsdConfig>> {
|
||||
// 1. 验证请求
|
||||
req.validate()?;
|
||||
|
||||
// 2. 获取旧配置
|
||||
let old_msd_config = state.config.get().msd.clone();
|
||||
|
||||
// 3. 应用更新到配置存储
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -34,10 +27,8 @@ pub async fn update_msd_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 4. 获取新配置
|
||||
let new_msd_config = state.config.get().msd.clone();
|
||||
|
||||
// 5. 应用到子系统(热重载)
|
||||
if let Err(e) = apply_msd_config(&state, &old_msd_config, &new_msd_config).await {
|
||||
tracing::error!("Failed to apply MSD config: {}", e);
|
||||
}
|
||||
|
||||
@@ -7,13 +7,11 @@ use crate::state::AppState;
|
||||
use super::apply::apply_rtsp_config;
|
||||
use super::types::{RtspConfigResponse, RtspConfigUpdate, RtspStatusResponse};
|
||||
|
||||
/// Get RTSP config
|
||||
pub async fn get_rtsp_config(State(state): State<Arc<AppState>>) -> Json<RtspConfigResponse> {
|
||||
let config = state.config.get();
|
||||
Json(RtspConfigResponse::from(&config.rtsp))
|
||||
}
|
||||
|
||||
/// Get RTSP status (config + service status)
|
||||
pub async fn get_rtsp_status(State(state): State<Arc<AppState>>) -> Json<RtspStatusResponse> {
|
||||
let config = state.config.get().rtsp.clone();
|
||||
let status = {
|
||||
@@ -28,7 +26,6 @@ pub async fn get_rtsp_status(State(state): State<Arc<AppState>>) -> Json<RtspSta
|
||||
Json(RtspStatusResponse::new(&config, status))
|
||||
}
|
||||
|
||||
/// Update RTSP config
|
||||
pub async fn update_rtsp_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<RtspConfigUpdate>,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! RustDesk 配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -10,18 +8,14 @@ use crate::state::AppState;
|
||||
use super::apply::apply_rustdesk_config;
|
||||
use super::types::RustDeskConfigUpdate;
|
||||
|
||||
/// RustDesk 配置响应(隐藏敏感信息)
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct RustDeskConfigResponse {
|
||||
pub enabled: bool,
|
||||
pub rendezvous_server: String,
|
||||
pub relay_server: Option<String>,
|
||||
pub device_id: String,
|
||||
/// 是否已设置密码
|
||||
pub has_password: bool,
|
||||
/// 是否已设置密钥对
|
||||
pub has_keypair: bool,
|
||||
/// 是否已设置 relay key
|
||||
pub has_relay_key: bool,
|
||||
}
|
||||
|
||||
@@ -39,7 +33,6 @@ impl From<&RustDeskConfig> for RustDeskConfigResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/// RustDesk 状态响应
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct RustDeskStatusResponse {
|
||||
pub config: RustDeskConfigResponse,
|
||||
@@ -47,20 +40,17 @@ pub struct RustDeskStatusResponse {
|
||||
pub rendezvous_status: Option<String>,
|
||||
}
|
||||
|
||||
/// 获取 RustDesk 配置
|
||||
pub async fn get_rustdesk_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Json<RustDeskConfigResponse> {
|
||||
Json(RustDeskConfigResponse::from(&state.config.get().rustdesk))
|
||||
}
|
||||
|
||||
/// 获取 RustDesk 完整状态(配置 + 服务状态)
|
||||
pub async fn get_rustdesk_status(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Json<RustDeskStatusResponse> {
|
||||
let config = state.config.get().rustdesk.clone();
|
||||
|
||||
// 获取服务状态
|
||||
let (service_status, rendezvous_status) = {
|
||||
let guard = state.rustdesk.read().await;
|
||||
if let Some(ref service) = *guard {
|
||||
@@ -79,18 +69,14 @@ pub async fn get_rustdesk_status(
|
||||
})
|
||||
}
|
||||
|
||||
/// 更新 RustDesk 配置
|
||||
pub async fn update_rustdesk_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<RustDeskConfigUpdate>,
|
||||
) -> Result<Json<RustDeskConfigResponse>> {
|
||||
// 1. 验证请求
|
||||
req.validate()?;
|
||||
|
||||
// 2. 获取旧配置
|
||||
let old_config = state.config.get().rustdesk.clone();
|
||||
|
||||
// 3. 应用更新到配置存储
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -98,15 +84,12 @@ pub async fn update_rustdesk_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 4. 获取新配置
|
||||
let new_config = state.config.get().rustdesk.clone();
|
||||
|
||||
// 5. 应用到子系统(热重载)
|
||||
if let Err(e) = apply_rustdesk_config(&state, &old_config, &new_config).await {
|
||||
tracing::error!("Failed to apply RustDesk config: {}", e);
|
||||
}
|
||||
|
||||
// Share a non-sensitive summary for frontend UX
|
||||
let constraints = state.stream_manager.codec_constraints().await;
|
||||
if constraints.rustdesk_enabled || constraints.rtsp_enabled {
|
||||
tracing::info!(
|
||||
@@ -118,7 +101,6 @@ pub async fn update_rustdesk_config(
|
||||
Ok(Json(RustDeskConfigResponse::from(&new_config)))
|
||||
}
|
||||
|
||||
/// 重新生成设备 ID
|
||||
pub async fn regenerate_device_id(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<RustDeskConfigResponse>> {
|
||||
@@ -133,7 +115,6 @@ pub async fn regenerate_device_id(
|
||||
Ok(Json(RustDeskConfigResponse::from(&new_config)))
|
||||
}
|
||||
|
||||
/// 重新生成设备密码
|
||||
pub async fn regenerate_device_password(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<RustDeskConfigResponse>> {
|
||||
@@ -148,7 +129,6 @@ pub async fn regenerate_device_password(
|
||||
Ok(Json(RustDeskConfigResponse::from(&new_config)))
|
||||
}
|
||||
|
||||
/// 获取设备密码(已认证用户)
|
||||
pub async fn get_device_password(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
|
||||
let config = state.config.get().rustdesk.clone();
|
||||
Json(serde_json::json!({
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Stream 配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -9,24 +7,19 @@ use crate::state::AppState;
|
||||
use super::apply::apply_stream_config;
|
||||
use super::types::{StreamConfigResponse, StreamConfigUpdate};
|
||||
|
||||
/// 获取 Stream 配置
|
||||
pub async fn get_stream_config(State(state): State<Arc<AppState>>) -> Json<StreamConfigResponse> {
|
||||
let config = state.config.get();
|
||||
Json(StreamConfigResponse::from(&config.stream))
|
||||
}
|
||||
|
||||
/// 更新 Stream 配置
|
||||
pub async fn update_stream_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<StreamConfigUpdate>,
|
||||
) -> Result<Json<StreamConfigResponse>> {
|
||||
// 1. 验证请求
|
||||
req.validate()?;
|
||||
|
||||
// 2. 获取旧配置
|
||||
let old_stream_config = state.config.get().stream.clone();
|
||||
|
||||
// 3. 应用更新到配置存储
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -34,15 +27,12 @@ pub async fn update_stream_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 4. 获取新配置
|
||||
let new_stream_config = state.config.get().stream.clone();
|
||||
|
||||
// 5. 应用到子系统(热重载)
|
||||
if let Err(e) = apply_stream_config(&state, &old_stream_config, &new_stream_config).await {
|
||||
tracing::error!("Failed to apply stream config: {}", e);
|
||||
}
|
||||
|
||||
// 6. Enforce codec constraints after any stream config update
|
||||
if let Err(e) = super::apply::enforce_stream_codec_constraints(&state).await {
|
||||
tracing::error!("Failed to enforce stream codec constraints: {}", e);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,11 @@ use crate::config::*;
|
||||
use crate::error::AppError;
|
||||
use crate::rtsp::RtspServiceStatus;
|
||||
use crate::rustdesk::config::RustDeskConfig;
|
||||
use crate::video::encoder::BitratePreset;
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use typeshare::typeshare;
|
||||
|
||||
// ===== Auth Config =====
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AuthConfigUpdate {
|
||||
@@ -27,7 +25,6 @@ impl AuthConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Video Config =====
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct VideoConfigUpdate {
|
||||
@@ -92,8 +89,6 @@ impl VideoConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Stream Config =====
|
||||
|
||||
/// Stream configuration response (includes has_turn_password)
|
||||
#[typeshare]
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
@@ -212,8 +207,6 @@ impl StreamConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== HID Config =====
|
||||
|
||||
/// OTG USB device descriptor configuration update
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -364,7 +357,6 @@ impl HidConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== MSD Config =====
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MsdConfigUpdate {
|
||||
@@ -398,8 +390,6 @@ impl MsdConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== ATX Config =====
|
||||
|
||||
/// Update for a single ATX key configuration
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -626,7 +616,6 @@ impl AtxConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Audio Config =====
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AudioConfigUpdate {
|
||||
@@ -660,8 +649,6 @@ impl AudioConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== RustDesk Config =====
|
||||
|
||||
/// hbbs/hbbr `-k` relay key: standard Base64 encoding of exactly 32 bytes (typically 44 chars with padding).
|
||||
fn validate_rustdesk_relay_key(key: &str) -> Result<(), AppError> {
|
||||
let decoded = STANDARD.decode(key.as_bytes()).map_err(|_| {
|
||||
@@ -758,7 +745,6 @@ impl RustDeskConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== RTSP Config =====
|
||||
#[typeshare]
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct RtspConfigResponse {
|
||||
@@ -876,8 +862,6 @@ impl RtspConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Web Config =====
|
||||
|
||||
/// Web server settings returned by `GET` / `PATCH /api/config/web`.
|
||||
///
|
||||
/// Public API shape: certificate paths on disk are not exposed. The full stored model is `WebConfig` in `config::schema`.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Video 配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -10,23 +8,18 @@ use crate::state::AppState;
|
||||
use super::apply::apply_video_config;
|
||||
use super::types::VideoConfigUpdate;
|
||||
|
||||
/// 获取 Video 配置
|
||||
pub async fn get_video_config(State(state): State<Arc<AppState>>) -> Json<VideoConfig> {
|
||||
Json(state.config.get().video.clone())
|
||||
}
|
||||
|
||||
/// 更新 Video 配置
|
||||
pub async fn update_video_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<VideoConfigUpdate>,
|
||||
) -> Result<Json<VideoConfig>> {
|
||||
// 1. 验证请求
|
||||
req.validate()?;
|
||||
|
||||
// 2. 获取旧配置
|
||||
let old_video_config = state.config.get().video.clone();
|
||||
|
||||
// 3. 应用更新到配置存储
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -34,10 +27,8 @@ pub async fn update_video_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 4. 获取新配置
|
||||
let new_video_config = state.config.get().video.clone();
|
||||
|
||||
// 5. 应用到子系统(热重载)
|
||||
if let Err(e) = apply_video_config(&state, &old_video_config, &new_video_config).await {
|
||||
tracing::error!("Failed to apply video config: {}", e);
|
||||
// 根据用户选择,仅记录错误,不回滚
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Web 服务器配置 Handler
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use axum_server::tls_rustls::RustlsConfig;
|
||||
use std::sync::Arc;
|
||||
@@ -9,14 +7,10 @@ use crate::state::AppState;
|
||||
|
||||
use super::types::{WebConfigResponse, WebConfigUpdate};
|
||||
|
||||
/// 获取 Web 配置
|
||||
pub async fn get_web_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Json<WebConfigResponse> {
|
||||
pub async fn get_web_config(State(state): State<Arc<AppState>>) -> Json<WebConfigResponse> {
|
||||
Json(WebConfigResponse::from_stored(&state.config.get().web))
|
||||
}
|
||||
|
||||
/// 更新 Web 配置(支持 PEM 证书上传)
|
||||
pub async fn update_web_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<WebConfigUpdate>,
|
||||
@@ -27,9 +21,13 @@ pub async fn update_web_config(
|
||||
// Some(Some((cert, key))) = write new cert
|
||||
// Some(None) = clear custom cert
|
||||
// None = no cert change
|
||||
let cert_path_update: Option<Option<(String, String)>> =
|
||||
if let (Some(cert_pem), Some(key_pem)) = (&req.ssl_cert_pem, &req.ssl_key_pem) {
|
||||
RustlsConfig::from_pem(cert_pem.as_bytes().to_vec(), key_pem.as_bytes().to_vec())
|
||||
let cert_path_update: Option<Option<(String, String)>> = if let (
|
||||
Some(cert_pem),
|
||||
Some(key_pem),
|
||||
) =
|
||||
(&req.ssl_cert_pem, &req.ssl_key_pem)
|
||||
{
|
||||
RustlsConfig::from_pem(cert_pem.as_bytes().to_vec(), key_pem.as_bytes().to_vec())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::BadRequest(
|
||||
@@ -39,30 +37,30 @@ pub async fn update_web_config(
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
let cert_dir = state.data_dir().join("certs");
|
||||
tokio::fs::create_dir_all(&cert_dir)
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to create cert dir: {e}")))?;
|
||||
let cert_path = cert_dir.join("custom.crt");
|
||||
let key_path = cert_dir.join("custom.key");
|
||||
tokio::fs::write(&cert_path, cert_pem.as_bytes())
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to write certificate: {e}")))?;
|
||||
tokio::fs::write(&key_path, key_pem.as_bytes())
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to write private key: {e}")))?;
|
||||
Some(Some((
|
||||
cert_path.to_string_lossy().into_owned(),
|
||||
key_path.to_string_lossy().into_owned(),
|
||||
)))
|
||||
} else if req.clear_custom_cert.unwrap_or(false) {
|
||||
let cert_dir = state.data_dir().join("certs");
|
||||
let _ = tokio::fs::remove_file(cert_dir.join("custom.crt")).await;
|
||||
let _ = tokio::fs::remove_file(cert_dir.join("custom.key")).await;
|
||||
Some(None)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cert_dir = state.data_dir().join("certs");
|
||||
tokio::fs::create_dir_all(&cert_dir)
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to create cert dir: {e}")))?;
|
||||
let cert_path = cert_dir.join("custom.crt");
|
||||
let key_path = cert_dir.join("custom.key");
|
||||
tokio::fs::write(&cert_path, cert_pem.as_bytes())
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to write certificate: {e}")))?;
|
||||
tokio::fs::write(&key_path, key_pem.as_bytes())
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to write private key: {e}")))?;
|
||||
Some(Some((
|
||||
cert_path.to_string_lossy().into_owned(),
|
||||
key_path.to_string_lossy().into_owned(),
|
||||
)))
|
||||
} else if req.clear_custom_cert.unwrap_or(false) {
|
||||
let cert_dir = state.data_dir().join("certs");
|
||||
let _ = tokio::fs::remove_file(cert_dir.join("custom.crt")).await;
|
||||
let _ = tokio::fs::remove_file(cert_dir.join("custom.key")).await;
|
||||
Some(None)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
state
|
||||
.config
|
||||
@@ -82,7 +80,9 @@ pub async fn update_web_config(
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(Json(WebConfigResponse::from_stored(&state.config.get().web)))
|
||||
Ok(Json(WebConfigResponse::from_stored(
|
||||
&state.config.get().web,
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
//! Device discovery handlers
|
||||
//!
|
||||
//! Provides API endpoints for discovering available hardware devices.
|
||||
|
||||
use axum::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -9,17 +5,10 @@ use crate::atx::{discover_devices, AtxDevices};
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::video::usb_reset;
|
||||
|
||||
/// GET /api/devices/atx - List available ATX devices
|
||||
///
|
||||
/// Returns lists of available GPIO chips and USB HID relay devices.
|
||||
pub async fn list_atx_devices() -> Json<AtxDevices> {
|
||||
Json(discover_devices())
|
||||
}
|
||||
|
||||
/// GET /api/devices/usb - List all USB devices
|
||||
///
|
||||
/// Enumerates USB devices from `/sys/bus/usb/devices/` with associated
|
||||
/// video device mappings.
|
||||
pub async fn list_usb_devices() -> Json<Vec<usb_reset::UsbDeviceInfo>> {
|
||||
Json(usb_reset::list_usb_devices())
|
||||
}
|
||||
@@ -30,11 +19,6 @@ pub struct UsbResetRequest {
|
||||
pub dev_num: u32,
|
||||
}
|
||||
|
||||
/// POST /api/devices/usb/reset - Reset a USB device via authorized cycle
|
||||
///
|
||||
/// Writes `0` then `1` to the device's `authorized` sysfs attribute,
|
||||
/// causing the kernel to deauthorize and re-authorize the device.
|
||||
/// Requires root or write access to sysfs.
|
||||
pub async fn reset_usb_device(Json(req): Json<UsbResetRequest>) -> Result<Json<serde_json::Value>> {
|
||||
usb_reset::reset_usb_device(req.bus_num, req.dev_num).map_err(|e| {
|
||||
AppError::VideoError(format!(
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Extension management API handlers
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
Json,
|
||||
@@ -15,12 +13,6 @@ use crate::extensions::{
|
||||
};
|
||||
use crate::state::AppState;
|
||||
|
||||
// ============================================================================
|
||||
// Get all extensions status
|
||||
// ============================================================================
|
||||
|
||||
/// Get status of all extensions
|
||||
/// GET /api/extensions
|
||||
pub async fn list_extensions(State(state): State<Arc<AppState>>) -> Json<ExtensionsStatus> {
|
||||
let config = state.config.get();
|
||||
let mgr = &state.extensions;
|
||||
@@ -44,12 +36,6 @@ pub async fn list_extensions(State(state): State<Arc<AppState>>) -> Json<Extensi
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Individual extension status
|
||||
// ============================================================================
|
||||
|
||||
/// Get status of a single extension
|
||||
/// GET /api/extensions/:id
|
||||
pub async fn get_extension(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
@@ -66,12 +52,6 @@ pub async fn get_extension(
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Start/Stop extensions
|
||||
// ============================================================================
|
||||
|
||||
/// Start an extension
|
||||
/// POST /api/extensions/:id/start
|
||||
pub async fn start_extension(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
@@ -83,20 +63,16 @@ pub async fn start_extension(
|
||||
let config = state.config.get();
|
||||
let mgr = &state.extensions;
|
||||
|
||||
// Start the extension
|
||||
mgr.start(ext_id, &config.extensions)
|
||||
.await
|
||||
.map_err(AppError::Internal)?;
|
||||
|
||||
// Return updated status
|
||||
Ok(Json(ExtensionInfo {
|
||||
available: mgr.check_available(ext_id),
|
||||
status: mgr.status(ext_id).await,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Stop an extension
|
||||
/// POST /api/extensions/:id/stop
|
||||
pub async fn stop_extension(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
@@ -107,29 +83,20 @@ pub async fn stop_extension(
|
||||
|
||||
let mgr = &state.extensions;
|
||||
|
||||
// Stop the extension
|
||||
mgr.stop(ext_id).await.map_err(AppError::Internal)?;
|
||||
|
||||
// Return updated status
|
||||
Ok(Json(ExtensionInfo {
|
||||
available: mgr.check_available(ext_id),
|
||||
status: mgr.status(ext_id).await,
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Extension logs
|
||||
// ============================================================================
|
||||
|
||||
/// Query parameters for logs
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct LogsQuery {
|
||||
/// Number of lines to return (default: 100, max: 500)
|
||||
pub lines: Option<usize>,
|
||||
}
|
||||
|
||||
/// Get extension logs
|
||||
/// GET /api/extensions/:id/logs
|
||||
pub async fn get_extension_logs(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
@@ -145,20 +112,13 @@ pub async fn get_extension_logs(
|
||||
Ok(Json(ExtensionLogs { id: ext_id, logs }))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Update extension config
|
||||
// ============================================================================
|
||||
|
||||
/// Update ttyd config
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TtydConfigUpdate {
|
||||
pub enabled: Option<bool>,
|
||||
pub port: Option<u16>,
|
||||
pub shell: Option<String>,
|
||||
}
|
||||
|
||||
/// Update gostc config
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GostcConfigUpdate {
|
||||
@@ -168,7 +128,6 @@ pub struct GostcConfigUpdate {
|
||||
pub tls: Option<bool>,
|
||||
}
|
||||
|
||||
/// Update easytier config
|
||||
#[typeshare]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EasytierConfigUpdate {
|
||||
@@ -179,16 +138,12 @@ pub struct EasytierConfigUpdate {
|
||||
pub virtual_ip: Option<String>,
|
||||
}
|
||||
|
||||
/// Update ttyd configuration
|
||||
/// PATCH /api/extensions/ttyd/config
|
||||
pub async fn update_ttyd_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<TtydConfigUpdate>,
|
||||
) -> Result<Json<TtydConfig>> {
|
||||
// Get current config
|
||||
let was_enabled = state.config.get().extensions.ttyd.enabled;
|
||||
|
||||
// Update config
|
||||
state
|
||||
.config
|
||||
.update(|config| {
|
||||
@@ -196,9 +151,6 @@ pub async fn update_ttyd_config(
|
||||
if let Some(enabled) = req.enabled {
|
||||
ttyd.enabled = enabled;
|
||||
}
|
||||
if let Some(port) = req.port {
|
||||
ttyd.port = port;
|
||||
}
|
||||
if let Some(ref shell) = req.shell {
|
||||
ttyd.shell = shell.clone();
|
||||
}
|
||||
@@ -208,12 +160,9 @@ pub async fn update_ttyd_config(
|
||||
let new_config = state.config.get();
|
||||
let is_enabled = new_config.extensions.ttyd.enabled;
|
||||
|
||||
// Handle enable/disable state change
|
||||
if was_enabled && !is_enabled {
|
||||
// Was running, now disabled - stop it
|
||||
state.extensions.stop(ExtensionId::Ttyd).await.ok();
|
||||
} else if !was_enabled && is_enabled {
|
||||
// Was disabled, now enabled - start it
|
||||
if state.extensions.check_available(ExtensionId::Ttyd) {
|
||||
state
|
||||
.extensions
|
||||
@@ -226,8 +175,6 @@ pub async fn update_ttyd_config(
|
||||
Ok(Json(new_config.extensions.ttyd.clone()))
|
||||
}
|
||||
|
||||
/// Update gostc configuration
|
||||
/// PATCH /api/extensions/gostc/config
|
||||
pub async fn update_gostc_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<GostcConfigUpdate>,
|
||||
@@ -276,8 +223,6 @@ pub async fn update_gostc_config(
|
||||
Ok(Json(new_config.extensions.gostc.clone()))
|
||||
}
|
||||
|
||||
/// Update easytier configuration
|
||||
/// PATCH /api/extensions/easytier/config
|
||||
pub async fn update_easytier_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<EasytierConfigUpdate>,
|
||||
|
||||
@@ -14,16 +14,13 @@ use crate::config::{AppConfig, StreamMode};
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::state::AppState;
|
||||
use crate::update::{UpdateChannel, UpdateOverviewResponse, UpdateStatusResponse, UpgradeRequest};
|
||||
use crate::utils::{hostname_uname, list_dir_names, read_trimmed};
|
||||
use crate::video::codec_constraints::codec_to_id;
|
||||
use crate::video::encoder::{
|
||||
build_hardware_self_check_runtime_error, run_hardware_self_check, BitratePreset,
|
||||
VideoEncoderSelfCheckResponse,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Health & Info
|
||||
// ============================================================================
|
||||
|
||||
/// Health check response
|
||||
#[derive(Serialize)]
|
||||
pub struct HealthResponse {
|
||||
@@ -169,7 +166,7 @@ fn get_device_info() -> DeviceInfo {
|
||||
let mem_info = get_meminfo();
|
||||
|
||||
DeviceInfo {
|
||||
hostname: get_hostname(),
|
||||
hostname: hostname_uname(),
|
||||
cpu_model: get_cpu_model(),
|
||||
cpu_usage: get_cpu_usage(),
|
||||
memory_total: mem_info.total,
|
||||
@@ -178,13 +175,6 @@ fn get_device_info() -> DeviceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get system hostname
|
||||
fn get_hostname() -> String {
|
||||
nix::unistd::gethostname()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|_| "unknown".to_string())
|
||||
}
|
||||
|
||||
/// Get CPU model name from /proc/cpuinfo, fallback to device-tree model
|
||||
fn get_cpu_model() -> String {
|
||||
let cpuinfo = std::fs::read_to_string("/proc/cpuinfo").ok();
|
||||
@@ -451,10 +441,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Authentication
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginRequest {
|
||||
pub username: String,
|
||||
@@ -550,9 +536,9 @@ pub async fn auth_check(
|
||||
axum::Extension(session): axum::Extension<Session>,
|
||||
) -> Json<AuthCheckResponse> {
|
||||
// Get user info from user_id
|
||||
let username = match state.users.get(&session.user_id).await {
|
||||
Ok(Some(user)) => Some(user.username),
|
||||
_ => Some(session.user_id.clone()), // Fallback to user_id if user not found
|
||||
let username = match state.users.single_user().await {
|
||||
Ok(Some(user)) if user.id == session.user_id => Some(user.username),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Json(AuthCheckResponse {
|
||||
@@ -561,10 +547,6 @@ pub async fn auth_check(
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Setup
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SetupStatus {
|
||||
pub initialized: bool,
|
||||
@@ -630,7 +612,10 @@ pub async fn setup_init(
|
||||
}
|
||||
|
||||
// Create single system user
|
||||
state.users.create(&req.username, &req.password).await?;
|
||||
state
|
||||
.users
|
||||
.create_first_user(&req.username, &req.password)
|
||||
.await?;
|
||||
|
||||
// Update config
|
||||
state
|
||||
@@ -780,7 +765,10 @@ pub async fn setup_init(
|
||||
let audio_config = crate::audio::AudioControllerConfig {
|
||||
enabled: true,
|
||||
device: new_config.audio.device.clone(),
|
||||
quality: crate::audio::AudioQuality::from_str(&new_config.audio.quality),
|
||||
quality: new_config
|
||||
.audio
|
||||
.quality
|
||||
.parse::<crate::audio::AudioQuality>()?,
|
||||
};
|
||||
if let Err(e) = state.audio.update_config(audio_config).await {
|
||||
tracing::warn!("Failed to start audio during setup: {}", e);
|
||||
@@ -804,10 +792,6 @@ pub async fn setup_init(
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateConfigRequest {
|
||||
#[serde(flatten)]
|
||||
@@ -962,10 +946,6 @@ fn merge_json(
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Devices
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DeviceList {
|
||||
pub video: Vec<VideoDevice>,
|
||||
@@ -1165,10 +1145,6 @@ pub async fn list_devices(State(state): State<Arc<AppState>>) -> Json<DeviceList
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Stream Control
|
||||
// ============================================================================
|
||||
|
||||
use crate::video::streamer::StreamerStats;
|
||||
use axum::{
|
||||
body::Body,
|
||||
@@ -1224,11 +1200,7 @@ pub async fn stream_mode_get(State(state): State<Arc<AppState>>) -> Json<StreamM
|
||||
StreamMode::Mjpeg => "mjpeg".to_string(),
|
||||
StreamMode::WebRTC => {
|
||||
use crate::video::encoder::VideoCodecType;
|
||||
let codec = state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.current_video_codec()
|
||||
.await;
|
||||
let codec = state.stream_manager.current_video_codec().await;
|
||||
match codec {
|
||||
VideoCodecType::H264 => "h264".to_string(),
|
||||
VideoCodecType::H265 => "h265".to_string(),
|
||||
@@ -1300,11 +1272,7 @@ pub async fn stream_mode_set(
|
||||
// switch_mode_transaction treats this as "no switch needed" since StreamMode
|
||||
// is still WebRTC, so we handle codec change + event emission here.
|
||||
let current_mode = state.stream_manager.current_mode().await;
|
||||
let prev_codec = state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.current_video_codec()
|
||||
.await;
|
||||
let prev_codec = state.stream_manager.current_video_codec().await;
|
||||
|
||||
let codec_changed = video_codec.is_some_and(|c| c != prev_codec);
|
||||
let is_codec_only_switch =
|
||||
@@ -1312,12 +1280,7 @@ pub async fn stream_mode_set(
|
||||
|
||||
if let Some(codec) = video_codec {
|
||||
info!("Setting WebRTC video codec to {:?}", codec);
|
||||
if let Err(e) = state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.set_video_codec(codec)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = state.stream_manager.set_video_codec(codec).await {
|
||||
warn!("Failed to set video codec: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -1349,11 +1312,7 @@ pub async fn stream_mode_set(
|
||||
let active_mode_str = match state.stream_manager.current_mode().await {
|
||||
StreamMode::Mjpeg => "mjpeg".to_string(),
|
||||
StreamMode::WebRTC => {
|
||||
let codec = state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.current_video_codec()
|
||||
.await;
|
||||
let codec = state.stream_manager.current_video_codec().await;
|
||||
match codec {
|
||||
VideoCodecType::H264 => "h264".to_string(),
|
||||
VideoCodecType::H265 => "h265".to_string(),
|
||||
@@ -1452,11 +1411,7 @@ pub async fn stream_constraints_get(
|
||||
let current_mode = match current_mode {
|
||||
StreamMode::Mjpeg => "mjpeg".to_string(),
|
||||
StreamMode::WebRTC => {
|
||||
let codec = state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.current_video_codec()
|
||||
.await;
|
||||
let codec = state.stream_manager.current_video_codec().await;
|
||||
match codec {
|
||||
VideoCodecType::H264 => "h264".to_string(),
|
||||
VideoCodecType::H265 => "h265".to_string(),
|
||||
@@ -1509,7 +1464,6 @@ pub async fn stream_set_bitrate(
|
||||
// Apply to WebRTC streamer (real-time adjustment)
|
||||
if let Err(e) = state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.set_bitrate_preset(req.bitrate_preset)
|
||||
.await
|
||||
{
|
||||
@@ -1850,10 +1804,6 @@ fn create_mjpeg_part(jpeg_data: &[u8]) -> bytes::Bytes {
|
||||
buf.freeze()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WebRTC
|
||||
// ============================================================================
|
||||
|
||||
use crate::webrtc::signaling::{AnswerResponse, IceCandidateRequest, OfferRequest};
|
||||
|
||||
/// Create WebRTC session
|
||||
@@ -1872,11 +1822,7 @@ pub async fn webrtc_create_session(
|
||||
));
|
||||
}
|
||||
|
||||
let session_id = state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.create_session()
|
||||
.await?;
|
||||
let session_id = state.webrtc.create_session().await?;
|
||||
Ok(Json(CreateSessionResponse { session_id }))
|
||||
}
|
||||
|
||||
@@ -1894,7 +1840,7 @@ pub async fn webrtc_offer(
|
||||
|
||||
// Backward compatibility: `client_id` is treated as an existing session_id hint.
|
||||
// New clients should not pass it; each offer creates a fresh session.
|
||||
let webrtc = state.stream_manager.webrtc_streamer();
|
||||
let webrtc = &state.webrtc;
|
||||
let session_id = if let Some(client_id) = &req.client_id {
|
||||
// Reuse only when it matches an active session ID.
|
||||
if webrtc.get_session(client_id).await.is_some() {
|
||||
@@ -1923,8 +1869,7 @@ pub async fn webrtc_ice_candidate(
|
||||
Json(req): Json<IceCandidateRequest>,
|
||||
) -> Result<Json<LoginResponse>> {
|
||||
state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.webrtc
|
||||
.add_ice_candidate(&req.session_id, req.candidate)
|
||||
.await?;
|
||||
|
||||
@@ -1948,7 +1893,7 @@ pub struct WebRtcStatus {
|
||||
}
|
||||
|
||||
pub async fn webrtc_status(State(state): State<Arc<AppState>>) -> Json<WebRtcStatus> {
|
||||
let sessions = state.stream_manager.webrtc_streamer().list_sessions().await;
|
||||
let sessions = state.webrtc.list_sessions().await;
|
||||
Json(WebRtcStatus {
|
||||
session_count: sessions.len(),
|
||||
sessions: sessions
|
||||
@@ -1971,11 +1916,7 @@ pub async fn webrtc_close_session(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<CloseSessionRequest>,
|
||||
) -> Result<Json<LoginResponse>> {
|
||||
state
|
||||
.stream_manager
|
||||
.webrtc_streamer()
|
||||
.close_session(&req.session_id)
|
||||
.await?;
|
||||
state.webrtc.close_session(&req.session_id).await?;
|
||||
|
||||
Ok(Json(LoginResponse {
|
||||
success: true,
|
||||
@@ -2066,10 +2007,6 @@ pub async fn webrtc_ice_servers(State(state): State<Arc<AppState>>) -> Json<IceS
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HID Control
|
||||
// ============================================================================
|
||||
|
||||
/// HID status response
|
||||
#[derive(Serialize)]
|
||||
pub struct HidStatus {
|
||||
@@ -2144,24 +2081,6 @@ fn push_otg_check(
|
||||
});
|
||||
}
|
||||
|
||||
fn list_dir_names(path: &std::path::Path) -> Vec<String> {
|
||||
let mut names = std::fs::read_dir(path)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.filter_map(|entry| entry.file_name().into_string().ok())
|
||||
.collect::<Vec<_>>();
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
||||
fn read_trimmed(path: &std::path::Path) -> Option<String> {
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
}
|
||||
|
||||
fn proc_modules_has(module_name: &str) -> bool {
|
||||
std::fs::read_to_string("/proc/modules")
|
||||
.ok()
|
||||
@@ -2870,10 +2789,6 @@ pub async fn hid_reset(State(state): State<Arc<AppState>>) -> Result<Json<LoginR
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MSD (Mass Storage Device)
|
||||
// ============================================================================
|
||||
|
||||
use crate::msd::{
|
||||
DownloadProgress, DriveFile, DriveInfo, DriveInitRequest, ImageDownloadRequest, ImageInfo,
|
||||
ImageManager, MsdConnectRequest, MsdMode, MsdState, VentoyDrive,
|
||||
@@ -3073,10 +2988,6 @@ pub async fn msd_disconnect(State(state): State<Arc<AppState>>) -> Result<Json<L
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MSD Virtual Drive
|
||||
// ============================================================================
|
||||
|
||||
/// Get drive info
|
||||
pub async fn msd_drive_info(State(state): State<Arc<AppState>>) -> Result<Json<DriveInfo>> {
|
||||
let config = state.config.get();
|
||||
@@ -3261,10 +3172,6 @@ pub async fn msd_drive_mkdir(
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ATX (Power Control)
|
||||
// ============================================================================
|
||||
|
||||
use crate::atx::{AtxState, PowerStatus};
|
||||
|
||||
const WOL_HISTORY_MAX_ENTRIES: i64 = 50;
|
||||
@@ -3421,7 +3328,7 @@ async fn record_wol_history(state: &Arc<AppState>, mac_address: &str) -> Result<
|
||||
"#,
|
||||
)
|
||||
.bind(mac_address)
|
||||
.execute(state.config.pool())
|
||||
.execute(state.db.pool())
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
@@ -3435,7 +3342,7 @@ async fn record_wol_history(state: &Arc<AppState>, mac_address: &str) -> Result<
|
||||
"#,
|
||||
)
|
||||
.bind(WOL_HISTORY_MAX_ENTRIES)
|
||||
.execute(state.config.pool())
|
||||
.execute(state.db.pool())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -3488,7 +3395,7 @@ pub async fn atx_wol_history(
|
||||
"#,
|
||||
)
|
||||
.bind(limit as i64)
|
||||
.fetch_all(state.config.pool())
|
||||
.fetch_all(state.db.pool())
|
||||
.await?;
|
||||
|
||||
let history = rows
|
||||
@@ -3502,10 +3409,6 @@ pub async fn atx_wol_history(
|
||||
Ok(Json(WolHistoryResponse { history }))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Audio Control
|
||||
// ============================================================================
|
||||
|
||||
use crate::audio::{AudioQuality, AudioStatus};
|
||||
|
||||
/// Audio status response (re-exports AudioStatus from audio module)
|
||||
@@ -3554,7 +3457,7 @@ pub async fn set_audio_quality(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<SetAudioQualityRequest>,
|
||||
) -> Result<Json<LoginResponse>> {
|
||||
let quality = AudioQuality::from_str(&req.quality);
|
||||
let quality = req.quality.parse::<AudioQuality>()?;
|
||||
state.audio.set_quality(quality).await?;
|
||||
Ok(Json(LoginResponse {
|
||||
success: true,
|
||||
@@ -3588,10 +3491,6 @@ pub async fn list_audio_devices(
|
||||
Ok(Json(devices))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Password Management
|
||||
// ============================================================================
|
||||
|
||||
/// Change password request
|
||||
#[derive(Deserialize)]
|
||||
pub struct ChangePasswordRequest {
|
||||
@@ -3607,10 +3506,14 @@ pub async fn change_password(
|
||||
) -> Result<Json<LoginResponse>> {
|
||||
let current_user = state
|
||||
.users
|
||||
.get(&session.user_id)
|
||||
.single_user()
|
||||
.await?
|
||||
.ok_or_else(|| AppError::AuthError("User not found".to_string()))?;
|
||||
|
||||
if current_user.id != session.user_id {
|
||||
return Err(AppError::AuthError("Invalid session".to_string()));
|
||||
}
|
||||
|
||||
if req.new_password.len() < 4 {
|
||||
return Err(AppError::BadRequest(
|
||||
"Password must be at least 4 characters".to_string(),
|
||||
@@ -3654,10 +3557,14 @@ pub async fn change_username(
|
||||
) -> Result<Json<LoginResponse>> {
|
||||
let current_user = state
|
||||
.users
|
||||
.get(&session.user_id)
|
||||
.single_user()
|
||||
.await?
|
||||
.ok_or_else(|| AppError::AuthError("User not found".to_string()))?;
|
||||
|
||||
if current_user.id != session.user_id {
|
||||
return Err(AppError::AuthError("Invalid session".to_string()));
|
||||
}
|
||||
|
||||
if req.username.len() < 2 {
|
||||
return Err(AppError::BadRequest(
|
||||
"Username must be at least 2 characters".to_string(),
|
||||
@@ -3688,10 +3595,6 @@ pub async fn change_username(
|
||||
}))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// System Control
|
||||
// ============================================================================
|
||||
|
||||
/// Restart the application
|
||||
pub async fn system_restart(State(state): State<Arc<AppState>>) -> Json<LoginResponse> {
|
||||
info!("System restart requested via API");
|
||||
@@ -3738,10 +3641,6 @@ pub async fn system_restart(State(state): State<Arc<AppState>>) -> Json<LoginRes
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Online Update
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateOverviewQuery {
|
||||
pub channel: Option<UpdateChannel>,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Terminal proxy handler - reverse proxy to ttyd via Unix socket
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{
|
||||
@@ -21,7 +19,6 @@ use crate::error::AppError;
|
||||
use crate::extensions::TTYD_SOCKET_PATH;
|
||||
use crate::state::AppState;
|
||||
|
||||
/// Handle WebSocket upgrade for terminal
|
||||
pub async fn terminal_ws(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
OriginalUri(original_uri): OriginalUri,
|
||||
@@ -32,14 +29,12 @@ pub async fn terminal_ws(
|
||||
.map(|q| format!("?{}", q))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Use the tty subprotocol that ttyd expects
|
||||
// ttyd expects the `tty` WebSocket subprotocol
|
||||
ws.protocols(["tty"])
|
||||
.on_upgrade(move |socket| handle_terminal_websocket(socket, query_string))
|
||||
}
|
||||
|
||||
/// Handle terminal WebSocket connection - bridge browser and ttyd
|
||||
async fn handle_terminal_websocket(client_ws: WebSocket, query_string: String) {
|
||||
// Connect to ttyd Unix socket
|
||||
let unix_stream = match UnixStream::connect(TTYD_SOCKET_PATH).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
@@ -48,7 +43,6 @@ async fn handle_terminal_websocket(client_ws: WebSocket, query_string: String) {
|
||||
}
|
||||
};
|
||||
|
||||
// Build WebSocket request for ttyd with tty subprotocol
|
||||
let uri_str = format!("ws://localhost/api/terminal/ws{}", query_string);
|
||||
let mut request = match uri_str.into_client_request() {
|
||||
Ok(r) => r,
|
||||
@@ -62,7 +56,6 @@ async fn handle_terminal_websocket(client_ws: WebSocket, query_string: String) {
|
||||
.headers_mut()
|
||||
.insert("Sec-WebSocket-Protocol", HeaderValue::from_static("tty"));
|
||||
|
||||
// Create WebSocket connection to ttyd
|
||||
let ws_stream = match tokio_tungstenite::client_async(request, unix_stream).await {
|
||||
Ok((ws, _)) => ws,
|
||||
Err(e) => {
|
||||
@@ -71,11 +64,9 @@ async fn handle_terminal_websocket(client_ws: WebSocket, query_string: String) {
|
||||
}
|
||||
};
|
||||
|
||||
// Split both WebSocket connections
|
||||
let (mut client_tx, mut client_rx) = client_ws.split();
|
||||
let (mut ttyd_tx, mut ttyd_rx) = ws_stream.split();
|
||||
|
||||
// Forward messages from browser to ttyd
|
||||
let client_to_ttyd = tokio::spawn(async move {
|
||||
while let Some(msg) = client_rx.next().await {
|
||||
let ttyd_msg = match msg {
|
||||
@@ -96,7 +87,6 @@ async fn handle_terminal_websocket(client_ws: WebSocket, query_string: String) {
|
||||
}
|
||||
});
|
||||
|
||||
// Forward messages from ttyd to browser
|
||||
let ttyd_to_client = tokio::spawn(async move {
|
||||
while let Some(msg) = ttyd_rx.next().await {
|
||||
let client_msg = match msg {
|
||||
@@ -118,14 +108,12 @@ async fn handle_terminal_websocket(client_ws: WebSocket, query_string: String) {
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for either direction to complete
|
||||
tokio::select! {
|
||||
_ = client_to_ttyd => {}
|
||||
_ = ttyd_to_client => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy HTTP requests to ttyd
|
||||
pub async fn terminal_proxy(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
path: Option<Path<String>>,
|
||||
@@ -133,12 +121,10 @@ pub async fn terminal_proxy(
|
||||
) -> Result<Response, AppError> {
|
||||
let path_str = path.map(|p| p.0).unwrap_or_default();
|
||||
|
||||
// Connect to ttyd Unix socket
|
||||
let mut unix_stream = UnixStream::connect(TTYD_SOCKET_PATH)
|
||||
.await
|
||||
.map_err(|e| AppError::ServiceUnavailable(format!("ttyd not running: {}", e)))?;
|
||||
|
||||
// Build HTTP request to forward
|
||||
let method = req.method().as_str();
|
||||
let query = req
|
||||
.uri()
|
||||
@@ -151,7 +137,6 @@ pub async fn terminal_proxy(
|
||||
format!("/api/terminal/{}{}", path_str, query)
|
||||
};
|
||||
|
||||
// Forward relevant headers
|
||||
let mut headers_str = String::new();
|
||||
for (name, value) in req.headers() {
|
||||
if let Ok(v) = value.to_str() {
|
||||
@@ -170,20 +155,17 @@ pub async fn terminal_proxy(
|
||||
method, uri_path, headers_str
|
||||
);
|
||||
|
||||
// Send request
|
||||
unix_stream
|
||||
.write_all(http_request.as_bytes())
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to send request: {}", e)))?;
|
||||
|
||||
// Read response
|
||||
let mut response_buf = Vec::new();
|
||||
unix_stream
|
||||
.read_to_end(&mut response_buf)
|
||||
.await
|
||||
.map_err(|e| AppError::Internal(format!("Failed to read response: {}", e)))?;
|
||||
|
||||
// Parse HTTP response
|
||||
let response_str = String::from_utf8_lossy(&response_buf);
|
||||
let header_end = response_str
|
||||
.find("\r\n\r\n")
|
||||
@@ -192,7 +174,6 @@ pub async fn terminal_proxy(
|
||||
let headers_part = &response_str[..header_end];
|
||||
let body_start = header_end + 4;
|
||||
|
||||
// Parse status line
|
||||
let status_line = headers_part
|
||||
.lines()
|
||||
.next()
|
||||
@@ -203,11 +184,9 @@ pub async fn terminal_proxy(
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(200);
|
||||
|
||||
// Build response
|
||||
let mut builder =
|
||||
Response::builder().status(StatusCode::from_u16(status_code).unwrap_or(StatusCode::OK));
|
||||
|
||||
// Forward response headers
|
||||
for line in headers_part.lines().skip(1) {
|
||||
if let Some((name, value)) = line.split_once(':') {
|
||||
let name = name.trim();
|
||||
@@ -232,7 +211,6 @@ pub async fn terminal_proxy(
|
||||
.map_err(|e| AppError::Internal(format!("Failed to build response: {}", e)))
|
||||
}
|
||||
|
||||
/// Terminal index page
|
||||
pub async fn terminal_index(
|
||||
State(state): State<Arc<AppState>>,
|
||||
req: Request<Body>,
|
||||
|
||||
Reference in New Issue
Block a user