mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-07-29 20:51:46 +08:00
fix: 修复扩展服务开启和自启动状态错误
This commit is contained in:
@@ -643,8 +643,12 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let runtime_config = state.config.get();
|
let runtime_config = state.runtime_third_party_config().await;
|
||||||
let constraints = StreamCodecConstraints::from_config(&runtime_config);
|
let constraints = StreamCodecConstraints::from_config(&runtime_config);
|
||||||
|
state
|
||||||
|
.stream_manager
|
||||||
|
.set_runtime_codec_constraints(constraints.clone())
|
||||||
|
.await;
|
||||||
match enforce_constraints_with_stream_manager(&state.stream_manager, &constraints).await {
|
match enforce_constraints_with_stream_manager(&state.stream_manager, &constraints).await {
|
||||||
Ok(result) if result.changed => {
|
Ok(result) if result.changed => {
|
||||||
if let Some(message) = result.message {
|
if let Some(message) = result.message {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ use rtsp_types as rtsp;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::{broadcast, Mutex, RwLock};
|
use tokio::sync::{broadcast, Mutex, RwLock};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use crate::config::RtspConfig;
|
use crate::config::RtspConfig;
|
||||||
use crate::error::{AppError, Result};
|
use crate::error::{AppError, Result};
|
||||||
@@ -25,6 +27,8 @@ use super::types::RtspConnectionState;
|
|||||||
|
|
||||||
pub use super::types::RtspServiceStatus;
|
pub use super::types::RtspServiceStatus;
|
||||||
|
|
||||||
|
const RTSP_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
pub struct RtspService {
|
pub struct RtspService {
|
||||||
config: Arc<RwLock<RtspConfig>>,
|
config: Arc<RwLock<RtspConfig>>,
|
||||||
status: Arc<RwLock<RtspServiceStatus>>,
|
status: Arc<RwLock<RtspServiceStatus>>,
|
||||||
@@ -74,18 +78,37 @@ impl RtspService {
|
|||||||
tracing::debug!("Failed to request keyframe on RTSP start: {}", err);
|
tracing::debug!("Failed to request keyframe on RTSP start: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bind_addr = bind_socket_addr(&config.bind, config.port)
|
let bind_addr = match bind_socket_addr(&config.bind, config.port) {
|
||||||
.map_err(|e| AppError::BadRequest(format!("Invalid RTSP bind address: {}", e)))?;
|
Ok(addr) => addr,
|
||||||
|
Err(err) => {
|
||||||
|
let error = AppError::BadRequest(format!("Invalid RTSP bind address: {}", err));
|
||||||
|
*self.status.write().await = RtspServiceStatus::Error(error.to_string());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let listener = bind_tcp_listener(bind_addr).map_err(|e| {
|
let listener = match bind_tcp_listener(bind_addr) {
|
||||||
AppError::Io(io::Error::new(e.kind(), format!("RTSP bind failed: {}", e)))
|
Ok(listener) => listener,
|
||||||
})?;
|
Err(err) => {
|
||||||
let listener = TcpListener::from_std(listener).map_err(|e| {
|
let error = AppError::Io(io::Error::new(
|
||||||
AppError::Io(io::Error::new(
|
err.kind(),
|
||||||
e.kind(),
|
format!("RTSP bind failed: {}", err),
|
||||||
format!("RTSP listener setup failed: {}", e),
|
));
|
||||||
))
|
*self.status.write().await = RtspServiceStatus::Error(error.to_string());
|
||||||
})?;
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let listener = match TcpListener::from_std(listener) {
|
||||||
|
Ok(listener) => listener,
|
||||||
|
Err(err) => {
|
||||||
|
let error = AppError::Io(io::Error::new(
|
||||||
|
err.kind(),
|
||||||
|
format!("RTSP listener setup failed: {}", err),
|
||||||
|
));
|
||||||
|
*self.status.write().await = RtspServiceStatus::Error(error.to_string());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let service_config = self.config.clone();
|
let service_config = self.config.clone();
|
||||||
let video_manager = self.video_manager.clone();
|
let video_manager = self.video_manager.clone();
|
||||||
@@ -138,7 +161,7 @@ impl RtspService {
|
|||||||
pub async fn stop(&self) -> Result<()> {
|
pub async fn stop(&self) -> Result<()> {
|
||||||
let _ = self.shutdown_tx.send(());
|
let _ = self.shutdown_tx.send(());
|
||||||
if let Some(handle) = self.server_handle.lock().await.take() {
|
if let Some(handle) = self.server_handle.lock().await.take() {
|
||||||
handle.abort();
|
wait_for_server_stop(handle).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut client_handles = self.client_handles.lock().await;
|
let mut client_handles = self.client_handles.lock().await;
|
||||||
@@ -170,6 +193,42 @@ impl RtspService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_for_server_stop(mut handle: JoinHandle<()>) {
|
||||||
|
match tokio::time::timeout(RTSP_SHUTDOWN_TIMEOUT, &mut handle).await {
|
||||||
|
Ok(Ok(())) => {}
|
||||||
|
Ok(Err(err)) if err.is_cancelled() => {}
|
||||||
|
Ok(Err(err)) => tracing::warn!("RTSP server task ended with error: {}", err),
|
||||||
|
Err(_) => {
|
||||||
|
tracing::warn!("Timed out waiting for RTSP server task to stop");
|
||||||
|
handle.abort();
|
||||||
|
let _ = handle.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn waiting_for_server_stop_releases_listener() {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0")
|
||||||
|
.await
|
||||||
|
.expect("bind test listener");
|
||||||
|
let bind_addr = listener.local_addr().expect("read test listener address");
|
||||||
|
let (shutdown_tx, mut shutdown_rx) = broadcast::channel(1);
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
let _listener = listener;
|
||||||
|
let _ = shutdown_rx.recv().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
shutdown_tx.send(()).expect("send shutdown signal");
|
||||||
|
wait_for_server_stop(handle).await;
|
||||||
|
|
||||||
|
std::net::TcpListener::bind(bind_addr).expect("rebind released listener address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_client(
|
async fn handle_client(
|
||||||
mut stream: TcpStream,
|
mut stream: TcpStream,
|
||||||
peer: SocketAddr,
|
peer: SocketAddr,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use self::protocol::{make_local_addr, make_relay_response, make_request_relay};
|
|||||||
use self::rendezvous::{AddrMangle, RendezvousMediator, RendezvousStatus};
|
use self::rendezvous::{AddrMangle, RendezvousMediator, RendezvousStatus};
|
||||||
|
|
||||||
const RELAY_CONNECT_TIMEOUT_MS: u64 = 10_000;
|
const RELAY_CONNECT_TIMEOUT_MS: u64 = 10_000;
|
||||||
|
const SERVICE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ServiceStatus {
|
pub enum ServiceStatus {
|
||||||
@@ -121,6 +122,10 @@ impl RustDeskService {
|
|||||||
self.connection_manager.connection_count()
|
self.connection_manager.connection_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_listening(&self) -> bool {
|
||||||
|
self.tcp_listener_handle.read().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start(&self) -> anyhow::Result<()> {
|
pub async fn start(&self) -> anyhow::Result<()> {
|
||||||
let config = self.config.read().clone();
|
let config = self.config.read().clone();
|
||||||
|
|
||||||
@@ -169,7 +174,13 @@ impl RustDeskService {
|
|||||||
|
|
||||||
*self.rendezvous.write() = Some(mediator.clone());
|
*self.rendezvous.write() = Some(mediator.clone());
|
||||||
|
|
||||||
let (tcp_handles, listen_port) = self.start_tcp_listener_with_port().await?;
|
let (tcp_handles, listen_port) = match self.start_tcp_listener_with_port().await {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(err) => {
|
||||||
|
*self.status.write() = ServiceStatus::Error(err.to_string());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
*self.tcp_listener_handle.write() = Some(tcp_handles);
|
*self.tcp_listener_handle.write() = Some(tcp_handles);
|
||||||
|
|
||||||
mediator.set_listen_port(listen_port);
|
mediator.set_listen_port(listen_port);
|
||||||
@@ -383,13 +394,15 @@ impl RustDeskService {
|
|||||||
mediator.stop();
|
mediator.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(handle) = self.rendezvous_handle.write().take() {
|
let rendezvous_handle = self.rendezvous_handle.write().take();
|
||||||
handle.abort();
|
if let Some(handle) = rendezvous_handle {
|
||||||
|
wait_for_service_task(handle, "rendezvous").await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(handles) = self.tcp_listener_handle.write().take() {
|
let tcp_listener_handles = self.tcp_listener_handle.write().take();
|
||||||
|
if let Some(handles) = tcp_listener_handles {
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
handle.abort();
|
wait_for_service_task(handle, "TCP listener").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,6 +467,19 @@ impl RustDeskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_for_service_task(mut handle: JoinHandle<()>, task_name: &str) {
|
||||||
|
match tokio::time::timeout(SERVICE_SHUTDOWN_TIMEOUT, &mut handle).await {
|
||||||
|
Ok(Ok(())) => {}
|
||||||
|
Ok(Err(err)) if err.is_cancelled() => {}
|
||||||
|
Ok(Err(err)) => warn!("RustDesk {} task ended with error: {}", task_name, err),
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Timed out waiting for RustDesk {} task to stop", task_name);
|
||||||
|
handle.abort();
|
||||||
|
let _ = handle.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn rustdesk_relay_key(config: &Arc<RwLock<RustDeskConfig>>) -> String {
|
fn rustdesk_relay_key(config: &Arc<RwLock<RustDeskConfig>>) -> String {
|
||||||
config.read().relay_key.clone().unwrap_or_default()
|
config.read().relay_key.clone().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/state.rs
27
src/state.rs
@@ -150,6 +150,33 @@ impl AppState {
|
|||||||
&self.data_dir
|
&self.data_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn runtime_third_party_config(&self) -> crate::config::AppConfig {
|
||||||
|
let mut config = self.config.get().as_ref().clone();
|
||||||
|
|
||||||
|
config.rustdesk.enabled = self
|
||||||
|
.rustdesk
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|service| service.is_listening());
|
||||||
|
config.vnc.enabled = match self.vnc.read().await.as_ref() {
|
||||||
|
Some(service) => matches!(
|
||||||
|
service.status().await,
|
||||||
|
crate::vnc::VncServiceStatus::Starting | crate::vnc::VncServiceStatus::Running
|
||||||
|
),
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
config.rtsp.enabled = match self.rtsp.read().await.as_ref() {
|
||||||
|
Some(service) => matches!(
|
||||||
|
service.status().await,
|
||||||
|
crate::rtsp::RtspServiceStatus::Starting | crate::rtsp::RtspServiceStatus::Running
|
||||||
|
),
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
pub fn subscribe_device_info(&self) -> watch::Receiver<Option<SystemEvent>> {
|
pub fn subscribe_device_info(&self) -> watch::Receiver<Option<SystemEvent>> {
|
||||||
self.device_info_tx.subscribe()
|
self.device_info_tx.subscribe()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ pub struct VideoStreamManager {
|
|||||||
events: RwLock<Option<Arc<EventBus>>>,
|
events: RwLock<Option<Arc<EventBus>>>,
|
||||||
/// Configuration store
|
/// Configuration store
|
||||||
config_store: RwLock<Option<ConfigStore>>,
|
config_store: RwLock<Option<ConfigStore>>,
|
||||||
|
/// Codec constraints derived from services that are actually running.
|
||||||
|
runtime_codec_constraints: RwLock<Option<StreamCodecConstraints>>,
|
||||||
/// Mode switching lock to prevent concurrent switch requests
|
/// Mode switching lock to prevent concurrent switch requests
|
||||||
switching: AtomicBool,
|
switching: AtomicBool,
|
||||||
/// Current mode switch transaction ID (set while switching=true)
|
/// Current mode switch transaction ID (set while switching=true)
|
||||||
@@ -118,6 +120,7 @@ impl VideoStreamManager {
|
|||||||
webrtc_streamer,
|
webrtc_streamer,
|
||||||
events: RwLock::new(None),
|
events: RwLock::new(None),
|
||||||
config_store: RwLock::new(None),
|
config_store: RwLock::new(None),
|
||||||
|
runtime_codec_constraints: RwLock::new(None),
|
||||||
switching: AtomicBool::new(false),
|
switching: AtomicBool::new(false),
|
||||||
transition_id: RwLock::new(None),
|
transition_id: RwLock::new(None),
|
||||||
})
|
})
|
||||||
@@ -144,8 +147,16 @@ impl VideoStreamManager {
|
|||||||
*self.config_store.write().await = Some(config);
|
*self.config_store.write().await = Some(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current stream codec constraints derived from global configuration.
|
pub async fn set_runtime_codec_constraints(&self, constraints: StreamCodecConstraints) {
|
||||||
|
*self.runtime_codec_constraints.write().await = Some(constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current stream codec constraints derived from running services.
|
||||||
pub async fn codec_constraints(&self) -> StreamCodecConstraints {
|
pub async fn codec_constraints(&self) -> StreamCodecConstraints {
|
||||||
|
if let Some(constraints) = self.runtime_codec_constraints.read().await.as_ref() {
|
||||||
|
return constraints.clone();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref config_store) = *self.config_store.read().await {
|
if let Some(ref config_store) = *self.config_store.read().await {
|
||||||
let config = config_store.get();
|
let config = config_store.get();
|
||||||
StreamCodecConstraints::from_config(&config)
|
StreamCodecConstraints::from_config(&config)
|
||||||
|
|||||||
@@ -121,20 +121,36 @@ impl VncService {
|
|||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bind_addr = bind_socket_addr(&config.bind, config.port)
|
let bind_addr = match bind_socket_addr(&config.bind, config.port) {
|
||||||
.map_err(|e| AppError::BadRequest(format!("Invalid VNC bind address: {}", e)))?;
|
Ok(addr) => addr,
|
||||||
let listener = bind_tcp_listener(bind_addr).map_err(|e| {
|
Err(err) => {
|
||||||
AppError::Io(std::io::Error::new(
|
let error = AppError::BadRequest(format!("Invalid VNC bind address: {}", err));
|
||||||
e.kind(),
|
*self.status.write().await = VncServiceStatus::Error(error.to_string());
|
||||||
format!("VNC bind failed: {}", e),
|
return Err(error);
|
||||||
))
|
}
|
||||||
})?;
|
};
|
||||||
let listener = TcpListener::from_std(listener).map_err(|e| {
|
let listener = match bind_tcp_listener(bind_addr) {
|
||||||
AppError::Io(std::io::Error::new(
|
Ok(listener) => listener,
|
||||||
e.kind(),
|
Err(err) => {
|
||||||
format!("VNC listener setup failed: {}", e),
|
let error = AppError::Io(std::io::Error::new(
|
||||||
))
|
err.kind(),
|
||||||
})?;
|
format!("VNC bind failed: {}", err),
|
||||||
|
));
|
||||||
|
*self.status.write().await = VncServiceStatus::Error(error.to_string());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let listener = match TcpListener::from_std(listener) {
|
||||||
|
Ok(listener) => listener,
|
||||||
|
Err(err) => {
|
||||||
|
let error = AppError::Io(std::io::Error::new(
|
||||||
|
err.kind(),
|
||||||
|
format!("VNC listener setup failed: {}", err),
|
||||||
|
));
|
||||||
|
*self.status.write().await = VncServiceStatus::Error(error.to_string());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let config_ref = self.config.clone();
|
let config_ref = self.config.clone();
|
||||||
let video_manager = self.video_manager.clone();
|
let video_manager = self.video_manager.clone();
|
||||||
|
|||||||
@@ -14,11 +14,33 @@ use tokio::sync::{Mutex, OwnedMutexGuard};
|
|||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct ConfigApplyOptions {
|
pub struct ConfigApplyOptions {
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
|
pub preserve_service_state: bool,
|
||||||
|
pub runtime_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigApplyOptions {
|
impl ConfigApplyOptions {
|
||||||
pub const fn forced() -> Self {
|
pub const fn forced() -> Self {
|
||||||
Self { force: true }
|
Self {
|
||||||
|
force: true,
|
||||||
|
preserve_service_state: false,
|
||||||
|
runtime_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn preserving_service_state() -> Self {
|
||||||
|
Self {
|
||||||
|
force: false,
|
||||||
|
preserve_service_state: true,
|
||||||
|
runtime_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn runtime_only() -> Self {
|
||||||
|
Self {
|
||||||
|
force: false,
|
||||||
|
preserve_service_state: false,
|
||||||
|
runtime_only: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,13 +471,27 @@ pub async fn apply_audio_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn enforce_stream_codec_constraints(state: &Arc<AppState>) -> Result<Option<String>> {
|
pub async fn enforce_stream_codec_constraints(state: &Arc<AppState>) -> Result<Option<String>> {
|
||||||
let config = state.config.get();
|
let config = state.runtime_third_party_config().await;
|
||||||
let constraints = StreamCodecConstraints::from_config(&config);
|
let constraints = StreamCodecConstraints::from_config(&config);
|
||||||
|
state
|
||||||
|
.stream_manager
|
||||||
|
.set_runtime_codec_constraints(constraints.clone())
|
||||||
|
.await;
|
||||||
let enforcement =
|
let enforcement =
|
||||||
enforce_constraints_with_stream_manager(&state.stream_manager, &constraints).await?;
|
enforce_constraints_with_stream_manager(&state.stream_manager, &constraints).await?;
|
||||||
Ok(enforcement.message)
|
Ok(enforcement.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn validate_runtime_candidate<T>(
|
||||||
|
state: &Arc<AppState>,
|
||||||
|
apply: impl FnOnce(&mut crate::config::AppConfig, T),
|
||||||
|
config: T,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut candidate = state.runtime_third_party_config().await;
|
||||||
|
apply(&mut candidate, config);
|
||||||
|
validate_third_party_codec_compatibility(&candidate)
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_rustdesk_candidate(
|
fn validate_rustdesk_candidate(
|
||||||
state: &Arc<AppState>,
|
state: &Arc<AppState>,
|
||||||
new_config: &crate::rustdesk::config::RustDeskConfig,
|
new_config: &crate::rustdesk::config::RustDeskConfig,
|
||||||
@@ -485,12 +521,26 @@ pub async fn apply_rustdesk_config(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!("Applying RustDesk config changes...");
|
tracing::info!("Applying RustDesk config changes...");
|
||||||
|
|
||||||
|
if options.runtime_only {
|
||||||
|
validate_runtime_candidate(
|
||||||
|
state,
|
||||||
|
|candidate, config| candidate.rustdesk = config,
|
||||||
|
new_config.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
validate_rustdesk_candidate(state, new_config)?;
|
validate_rustdesk_candidate(state, new_config)?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut rustdesk_guard = state.rustdesk.write().await;
|
let mut rustdesk_guard = state.rustdesk.write().await;
|
||||||
let mut credentials_to_save = None;
|
let mut credentials_to_save = None;
|
||||||
|
let need_restart = options.force
|
||||||
|
|| old_config.codec != new_config.codec
|
||||||
|
|| 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 !new_config.enabled {
|
if !options.preserve_service_state && !new_config.enabled {
|
||||||
if let Some(ref service) = *rustdesk_guard {
|
if let Some(ref service) = *rustdesk_guard {
|
||||||
service
|
service
|
||||||
.stop()
|
.stop()
|
||||||
@@ -501,29 +551,25 @@ pub async fn apply_rustdesk_config(
|
|||||||
*rustdesk_guard = None;
|
*rustdesk_guard = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_config.enabled {
|
if !options.preserve_service_state && new_config.enabled {
|
||||||
let need_restart = options.force
|
|
||||||
|| old_config.codec != new_config.codec
|
|
||||||
|| 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() {
|
if rustdesk_guard.is_none() {
|
||||||
tracing::info!("Initializing RustDesk service...");
|
tracing::info!("Initializing RustDesk service...");
|
||||||
let service = crate::rustdesk::RustDeskService::new(
|
let service = std::sync::Arc::new(crate::rustdesk::RustDeskService::new(
|
||||||
new_config.clone(),
|
new_config.clone(),
|
||||||
state.stream_manager.clone(),
|
state.stream_manager.clone(),
|
||||||
state.hid.clone(),
|
state.hid.clone(),
|
||||||
state.audio.clone(),
|
state.audio.clone(),
|
||||||
);
|
));
|
||||||
|
*rustdesk_guard = Some(service.clone());
|
||||||
service.start().await.map_err(|e| {
|
service.start().await.map_err(|e| {
|
||||||
AppError::Config(format!("Failed to start RustDesk service: {}", e))
|
AppError::Config(format!("Failed to start RustDesk service: {}", e))
|
||||||
})?;
|
})?;
|
||||||
tracing::info!("RustDesk service started with ID: {}", new_config.device_id);
|
tracing::info!("RustDesk service started with ID: {}", new_config.device_id);
|
||||||
credentials_to_save = service.save_credentials();
|
credentials_to_save = service.save_credentials();
|
||||||
*rustdesk_guard = Some(std::sync::Arc::new(service));
|
} else {
|
||||||
} else if need_restart {
|
|
||||||
if let Some(ref service) = *rustdesk_guard {
|
if let Some(ref service) = *rustdesk_guard {
|
||||||
|
if service.is_listening() {
|
||||||
|
if need_restart {
|
||||||
service.restart(new_config.clone()).await.map_err(|e| {
|
service.restart(new_config.clone()).await.map_err(|e| {
|
||||||
AppError::Config(format!("Failed to restart RustDesk service: {}", e))
|
AppError::Config(format!("Failed to restart RustDesk service: {}", e))
|
||||||
})?;
|
})?;
|
||||||
@@ -531,9 +577,25 @@ pub async fn apply_rustdesk_config(
|
|||||||
"RustDesk service restarted with ID: {}",
|
"RustDesk service restarted with ID: {}",
|
||||||
new_config.device_id
|
new_config.device_id
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
service.update_config(new_config.clone());
|
||||||
|
service.start().await.map_err(|e| {
|
||||||
|
AppError::Config(format!("Failed to start RustDesk service: {}", e))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
credentials_to_save = service.save_credentials();
|
credentials_to_save = service.save_credentials();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if options.preserve_service_state && need_restart {
|
||||||
|
if let Some(ref service) = *rustdesk_guard {
|
||||||
|
let mut runtime_config = new_config.clone();
|
||||||
|
runtime_config.enabled = true;
|
||||||
|
service.restart(runtime_config).await.map_err(|e| {
|
||||||
|
AppError::Config(format!("Failed to restart RustDesk service: {}", e))
|
||||||
|
})?;
|
||||||
|
credentials_to_save = service.save_credentials();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(rustdesk_guard);
|
drop(rustdesk_guard);
|
||||||
@@ -567,11 +629,27 @@ pub async fn apply_vnc_config(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!("Applying VNC config changes...");
|
tracing::info!("Applying VNC config changes...");
|
||||||
|
|
||||||
|
if options.runtime_only {
|
||||||
|
validate_runtime_candidate(
|
||||||
|
state,
|
||||||
|
|candidate, config| candidate.vnc = config,
|
||||||
|
new_config.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
validate_vnc_candidate(state, new_config)?;
|
validate_vnc_candidate(state, new_config)?;
|
||||||
|
}
|
||||||
|
|
||||||
if new_config.enabled {
|
let runtime_config = state.runtime_third_party_config().await;
|
||||||
let mut candidate = state.config.get().as_ref().clone();
|
let will_run = if options.preserve_service_state {
|
||||||
|
runtime_config.vnc.enabled
|
||||||
|
} else {
|
||||||
|
new_config.enabled
|
||||||
|
};
|
||||||
|
if will_run {
|
||||||
|
let mut candidate = runtime_config;
|
||||||
candidate.vnc = new_config.clone();
|
candidate.vnc = new_config.clone();
|
||||||
|
candidate.vnc.enabled = true;
|
||||||
let constraints = StreamCodecConstraints::from_config(&candidate);
|
let constraints = StreamCodecConstraints::from_config(&candidate);
|
||||||
match enforce_constraints_with_stream_manager(&state.stream_manager, &constraints).await {
|
match enforce_constraints_with_stream_manager(&state.stream_manager, &constraints).await {
|
||||||
Ok(result) if result.changed => {
|
Ok(result) if result.changed => {
|
||||||
@@ -588,15 +666,6 @@ pub async fn apply_vnc_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut vnc_guard = state.vnc.write().await;
|
let mut vnc_guard = state.vnc.write().await;
|
||||||
|
|
||||||
if !new_config.enabled {
|
|
||||||
if let Some(ref service) = *vnc_guard {
|
|
||||||
service.stop().await?;
|
|
||||||
}
|
|
||||||
*vnc_guard = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_config.enabled {
|
|
||||||
let need_restart = options.force
|
let need_restart = options.force
|
||||||
|| old_config.bind != new_config.bind
|
|| old_config.bind != new_config.bind
|
||||||
|| old_config.port != new_config.port
|
|| old_config.port != new_config.port
|
||||||
@@ -604,20 +673,44 @@ pub async fn apply_vnc_config(
|
|||||||
|| old_config.password != new_config.password
|
|| old_config.password != new_config.password
|
||||||
|| old_config.allow_one_client != new_config.allow_one_client;
|
|| old_config.allow_one_client != new_config.allow_one_client;
|
||||||
|
|
||||||
|
if !options.preserve_service_state && !new_config.enabled {
|
||||||
|
if let Some(ref service) = *vnc_guard {
|
||||||
|
service.stop().await?;
|
||||||
|
}
|
||||||
|
*vnc_guard = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !options.preserve_service_state && new_config.enabled {
|
||||||
if vnc_guard.is_none() {
|
if vnc_guard.is_none() {
|
||||||
let service = crate::vnc::VncService::new(
|
let service = Arc::new(crate::vnc::VncService::new(
|
||||||
new_config.clone(),
|
new_config.clone(),
|
||||||
state.stream_manager.clone(),
|
state.stream_manager.clone(),
|
||||||
state.hid.clone(),
|
state.hid.clone(),
|
||||||
);
|
));
|
||||||
|
*vnc_guard = Some(service.clone());
|
||||||
service.start().await?;
|
service.start().await?;
|
||||||
*vnc_guard = Some(Arc::new(service));
|
|
||||||
tracing::info!("VNC service started");
|
tracing::info!("VNC service started");
|
||||||
} else if need_restart {
|
} else {
|
||||||
if let Some(ref service) = *vnc_guard {
|
if let Some(ref service) = *vnc_guard {
|
||||||
|
if matches!(
|
||||||
|
service.status().await,
|
||||||
|
crate::vnc::VncServiceStatus::Running
|
||||||
|
) {
|
||||||
|
if need_restart {
|
||||||
service.restart(new_config.clone()).await?;
|
service.restart(new_config.clone()).await?;
|
||||||
tracing::info!("VNC service restarted");
|
tracing::info!("VNC service restarted");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
service.update_config(new_config.clone()).await;
|
||||||
|
service.start().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if options.preserve_service_state && need_restart {
|
||||||
|
if let Some(ref service) = *vnc_guard {
|
||||||
|
let mut runtime_config = new_config.clone();
|
||||||
|
runtime_config.enabled = true;
|
||||||
|
service.restart(runtime_config).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,21 +730,18 @@ pub async fn apply_rtsp_config(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!("Applying RTSP config changes...");
|
tracing::info!("Applying RTSP config changes...");
|
||||||
|
|
||||||
|
if options.runtime_only {
|
||||||
|
validate_runtime_candidate(
|
||||||
|
state,
|
||||||
|
|candidate, config| candidate.rtsp = config,
|
||||||
|
new_config.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
validate_rtsp_candidate(state, new_config)?;
|
validate_rtsp_candidate(state, new_config)?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut rtsp_guard = state.rtsp.write().await;
|
let mut rtsp_guard = state.rtsp.write().await;
|
||||||
|
|
||||||
if !new_config.enabled {
|
|
||||||
if let Some(ref service) = *rtsp_guard {
|
|
||||||
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 = options.force
|
let need_restart = options.force
|
||||||
|| old_config.bind != new_config.bind
|
|| old_config.bind != new_config.bind
|
||||||
|| old_config.port != new_config.port
|
|| old_config.port != new_config.port
|
||||||
@@ -661,16 +751,46 @@ pub async fn apply_rtsp_config(
|
|||||||
|| old_config.password != new_config.password
|
|| old_config.password != new_config.password
|
||||||
|| old_config.allow_one_client != new_config.allow_one_client;
|
|| old_config.allow_one_client != new_config.allow_one_client;
|
||||||
|
|
||||||
|
if !options.preserve_service_state && !new_config.enabled {
|
||||||
|
if let Some(ref service) = *rtsp_guard {
|
||||||
|
service
|
||||||
|
.stop()
|
||||||
|
.await
|
||||||
|
.map_err(|e| AppError::Config(format!("Failed to stop RTSP service: {}", e)))?;
|
||||||
|
}
|
||||||
|
*rtsp_guard = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !options.preserve_service_state && new_config.enabled {
|
||||||
if rtsp_guard.is_none() {
|
if rtsp_guard.is_none() {
|
||||||
let service = RtspService::new(new_config.clone(), state.stream_manager.clone());
|
let service = Arc::new(RtspService::new(
|
||||||
|
new_config.clone(),
|
||||||
|
state.stream_manager.clone(),
|
||||||
|
));
|
||||||
|
*rtsp_guard = Some(service.clone());
|
||||||
service.start().await?;
|
service.start().await?;
|
||||||
tracing::info!("RTSP service started");
|
tracing::info!("RTSP service started");
|
||||||
*rtsp_guard = Some(Arc::new(service));
|
} else {
|
||||||
} else if need_restart {
|
|
||||||
if let Some(ref service) = *rtsp_guard {
|
if let Some(ref service) = *rtsp_guard {
|
||||||
|
if matches!(
|
||||||
|
service.status().await,
|
||||||
|
crate::rtsp::RtspServiceStatus::Running
|
||||||
|
) {
|
||||||
|
if need_restart {
|
||||||
service.restart(new_config.clone()).await?;
|
service.restart(new_config.clone()).await?;
|
||||||
tracing::info!("RTSP service restarted");
|
tracing::info!("RTSP service restarted");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
service.update_config(new_config.clone()).await;
|
||||||
|
service.start().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if options.preserve_service_state && need_restart {
|
||||||
|
if let Some(ref service) = *rtsp_guard {
|
||||||
|
let mut runtime_config = new_config.clone();
|
||||||
|
runtime_config.enabled = true;
|
||||||
|
service.restart(runtime_config).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async fn persist_and_apply(
|
|||||||
state,
|
state,
|
||||||
&old_config,
|
&old_config,
|
||||||
&stored_config,
|
&stored_config,
|
||||||
ConfigApplyOptions::forced(),
|
ConfigApplyOptions::preserving_service_state(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(stored_config)
|
Ok(stored_config)
|
||||||
@@ -76,10 +76,17 @@ pub async fn start_rtsp_service(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<RtspStatusResponse>> {
|
) -> Result<Json<RtspStatusResponse>> {
|
||||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?;
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?;
|
||||||
let current_config = state.config.get().rtsp.clone();
|
let stored_config = state.config.get().rtsp.clone();
|
||||||
let mut start_config = current_config.clone();
|
let runtime_config = state.runtime_third_party_config().await.rtsp;
|
||||||
|
let mut start_config = stored_config.clone();
|
||||||
start_config.enabled = true;
|
start_config.enabled = true;
|
||||||
let stored_config = persist_and_apply(&state, current_config, start_config).await?;
|
apply_rtsp_config(
|
||||||
|
&state,
|
||||||
|
&runtime_config,
|
||||||
|
&start_config,
|
||||||
|
ConfigApplyOptions::runtime_only(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let status = current_status(&state).await;
|
let status = current_status(&state).await;
|
||||||
|
|
||||||
Ok(Json(RtspStatusResponse::new(&stored_config, status)))
|
Ok(Json(RtspStatusResponse::new(&stored_config, status)))
|
||||||
@@ -89,11 +96,17 @@ pub async fn stop_rtsp_service(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<RtspStatusResponse>> {
|
) -> Result<Json<RtspStatusResponse>> {
|
||||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?;
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?;
|
||||||
let current_config = state.config.get().rtsp.clone();
|
let stored_config = state.config.get().rtsp.clone();
|
||||||
let mut stop_config = current_config.clone();
|
let runtime_config = state.runtime_third_party_config().await.rtsp;
|
||||||
|
let mut stop_config = stored_config.clone();
|
||||||
stop_config.enabled = false;
|
stop_config.enabled = false;
|
||||||
|
apply_rtsp_config(
|
||||||
let stored_config = persist_and_apply(&state, current_config, stop_config).await?;
|
&state,
|
||||||
|
&runtime_config,
|
||||||
|
&stop_config,
|
||||||
|
ConfigApplyOptions::runtime_only(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let status = current_status(&state).await;
|
let status = current_status(&state).await;
|
||||||
|
|
||||||
Ok(Json(RtspStatusResponse::new(&stored_config, status)))
|
Ok(Json(RtspStatusResponse::new(&stored_config, status)))
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async fn persist_and_apply(
|
|||||||
state,
|
state,
|
||||||
&old_config,
|
&old_config,
|
||||||
&stored_config,
|
&stored_config,
|
||||||
ConfigApplyOptions::forced(),
|
ConfigApplyOptions::preserving_service_state(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(stored_config)
|
Ok(stored_config)
|
||||||
@@ -168,10 +168,18 @@ pub async fn start_rustdesk_service(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<RustDeskStatusResponse>> {
|
) -> Result<Json<RustDeskStatusResponse>> {
|
||||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?;
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?;
|
||||||
let current_config = state.config.get().rustdesk.clone();
|
let stored_config = state.config.get().rustdesk.clone();
|
||||||
let mut start_config = current_config.clone();
|
let runtime_config = state.runtime_third_party_config().await.rustdesk;
|
||||||
|
let mut start_config = stored_config.clone();
|
||||||
start_config.enabled = true;
|
start_config.enabled = true;
|
||||||
let stored_config = persist_and_apply(&state, current_config, start_config).await?;
|
apply_rustdesk_config(
|
||||||
|
&state,
|
||||||
|
&runtime_config,
|
||||||
|
&start_config,
|
||||||
|
ConfigApplyOptions::runtime_only(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let stored_config = state.config.get().rustdesk.clone();
|
||||||
Ok(Json(current_status(&state, stored_config).await))
|
Ok(Json(current_status(&state, stored_config).await))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +187,16 @@ pub async fn stop_rustdesk_service(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<RustDeskStatusResponse>> {
|
) -> Result<Json<RustDeskStatusResponse>> {
|
||||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?;
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?;
|
||||||
let current_config = state.config.get().rustdesk.clone();
|
let stored_config = state.config.get().rustdesk.clone();
|
||||||
let mut stop_config = current_config.clone();
|
let runtime_config = state.runtime_third_party_config().await.rustdesk;
|
||||||
|
let mut stop_config = stored_config.clone();
|
||||||
stop_config.enabled = false;
|
stop_config.enabled = false;
|
||||||
|
apply_rustdesk_config(
|
||||||
let stored_config = persist_and_apply(&state, current_config, stop_config).await?;
|
&state,
|
||||||
|
&runtime_config,
|
||||||
|
&stop_config,
|
||||||
|
ConfigApplyOptions::runtime_only(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(Json(current_status(&state, stored_config).await))
|
Ok(Json(current_status(&state, stored_config).await))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async fn persist_and_apply(
|
|||||||
state,
|
state,
|
||||||
&old_config,
|
&old_config,
|
||||||
&stored_config,
|
&stored_config,
|
||||||
ConfigApplyOptions::forced(),
|
ConfigApplyOptions::preserving_service_state(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(stored_config)
|
Ok(stored_config)
|
||||||
@@ -76,13 +76,20 @@ pub async fn start_vnc_service(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<VncStatusResponse>> {
|
) -> Result<Json<VncStatusResponse>> {
|
||||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.vnc, "vnc")?;
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.vnc, "vnc")?;
|
||||||
let current_config = state.config.get().vnc.clone();
|
let stored_config = state.config.get().vnc.clone();
|
||||||
let mut start_config = current_config.clone();
|
let runtime_config = state.runtime_third_party_config().await.vnc;
|
||||||
|
let mut start_config = stored_config.clone();
|
||||||
start_config.enabled = true;
|
start_config.enabled = true;
|
||||||
if start_config.password.as_deref().unwrap_or("").is_empty() {
|
if start_config.password.as_deref().unwrap_or("").is_empty() {
|
||||||
start_config.password = current_config.password.clone();
|
start_config.password = stored_config.password.clone();
|
||||||
}
|
}
|
||||||
let stored_config = persist_and_apply(&state, current_config, start_config).await?;
|
apply_vnc_config(
|
||||||
|
&state,
|
||||||
|
&runtime_config,
|
||||||
|
&start_config,
|
||||||
|
ConfigApplyOptions::runtime_only(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let (status, connection_count) = current_status(&state).await;
|
let (status, connection_count) = current_status(&state).await;
|
||||||
|
|
||||||
Ok(Json(VncStatusResponse::new(
|
Ok(Json(VncStatusResponse::new(
|
||||||
@@ -96,11 +103,17 @@ pub async fn stop_vnc_service(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Json<VncStatusResponse>> {
|
) -> Result<Json<VncStatusResponse>> {
|
||||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.vnc, "vnc")?;
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.vnc, "vnc")?;
|
||||||
let current_config = state.config.get().vnc.clone();
|
let stored_config = state.config.get().vnc.clone();
|
||||||
let mut stop_config = current_config.clone();
|
let runtime_config = state.runtime_third_party_config().await.vnc;
|
||||||
|
let mut stop_config = stored_config.clone();
|
||||||
stop_config.enabled = false;
|
stop_config.enabled = false;
|
||||||
|
apply_vnc_config(
|
||||||
let stored_config = persist_and_apply(&state, current_config, stop_config).await?;
|
&state,
|
||||||
|
&runtime_config,
|
||||||
|
&stop_config,
|
||||||
|
ConfigApplyOptions::runtime_only(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Json(VncStatusResponse::new(
|
Ok(Json(VncStatusResponse::new(
|
||||||
&stored_config,
|
&stored_config,
|
||||||
|
|||||||
@@ -74,18 +74,9 @@ const loadingCodecs = ref(false)
|
|||||||
const backends = ref<EncoderBackendInfo[]>([])
|
const backends = ref<EncoderBackendInfo[]>([])
|
||||||
const constraints = ref<StreamConstraintsResponse | null>(null)
|
const constraints = ref<StreamConstraintsResponse | null>(null)
|
||||||
const currentEncoderBackend = computed(() => configStore.stream?.encoder || 'auto')
|
const currentEncoderBackend = computed(() => configStore.stream?.encoder || 'auto')
|
||||||
const isRtspEnabled = computed(() => {
|
const isServiceActive = (status: string | undefined) => status === 'starting' || status === 'running'
|
||||||
if (typeof configStore.rtspStatus?.config?.enabled === 'boolean') {
|
const isRtspEnabled = computed(() => isServiceActive(configStore.rtspStatus?.service_status))
|
||||||
return configStore.rtspStatus.config.enabled
|
const isRustdeskEnabled = computed(() => isServiceActive(configStore.rustdeskStatus?.service_status))
|
||||||
}
|
|
||||||
return !!configStore.rtspConfig?.enabled
|
|
||||||
})
|
|
||||||
const isRustdeskEnabled = computed(() => {
|
|
||||||
if (typeof configStore.rustdeskStatus?.config?.enabled === 'boolean') {
|
|
||||||
return configStore.rustdeskStatus.config.enabled
|
|
||||||
}
|
|
||||||
return !!configStore.rustdeskConfig?.enabled
|
|
||||||
})
|
|
||||||
const isRtspCodecLocked = computed(() => isRtspEnabled.value)
|
const isRtspCodecLocked = computed(() => isRtspEnabled.value)
|
||||||
const isRustdeskWebrtcLocked = computed(() => !isRtspEnabled.value && isRustdeskEnabled.value)
|
const isRustdeskWebrtcLocked = computed(() => !isRtspEnabled.value && isRustdeskEnabled.value)
|
||||||
const codecLockSources = computed(() => {
|
const codecLockSources = computed(() => {
|
||||||
|
|||||||
@@ -2376,7 +2376,7 @@ async function startRustdesk() {
|
|||||||
|
|
||||||
rustdeskLoading.value = true
|
rustdeskLoading.value = true
|
||||||
try {
|
try {
|
||||||
await configStore.updateRustdesk(rustdeskUpdatePayload(true))
|
await configStore.updateRustdesk(rustdeskUpdatePayload())
|
||||||
const status = await rustdeskConfigApi.start()
|
const status = await rustdeskConfigApi.start()
|
||||||
applyRustdeskStatus(status)
|
applyRustdeskStatus(status)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -2501,7 +2501,7 @@ async function saveRtspConfig() {
|
|||||||
async function startRtsp() {
|
async function startRtsp() {
|
||||||
rtspLoading.value = true
|
rtspLoading.value = true
|
||||||
try {
|
try {
|
||||||
await configStore.updateRtsp(rtspUpdatePayload(true))
|
await configStore.updateRtsp(rtspUpdatePayload())
|
||||||
const status = await rtspConfigApi.start()
|
const status = await rtspConfigApi.start()
|
||||||
applyRtspStatus(status)
|
applyRtspStatus(status)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -2578,7 +2578,7 @@ async function startVnc() {
|
|||||||
|
|
||||||
vncLoading.value = true
|
vncLoading.value = true
|
||||||
try {
|
try {
|
||||||
await configStore.updateVnc(vncUpdatePayload(true))
|
await configStore.updateVnc(vncUpdatePayload())
|
||||||
const status = await vncConfigApi.start()
|
const status = await vncConfigApi.start()
|
||||||
applyVncStatus(status)
|
applyVncStatus(status)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -4714,7 +4714,7 @@ watch(isWindows, () => {
|
|||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Label>{{ t('extensions.autoStart') }}</Label>
|
<Label>{{ t('extensions.autoStart') }}</Label>
|
||||||
<Switch v-model="rtspLocalConfig.enabled" :disabled="rtspStatus?.service_status === 'running'" />
|
<Switch v-model="rtspLocalConfig.enabled" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
|
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
|
||||||
<Label class="sm:text-right">{{ t('extensions.rtsp.bind') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rtsp.bind') }}</Label>
|
||||||
@@ -4781,7 +4781,7 @@ watch(isWindows, () => {
|
|||||||
<span class="shrink-0 font-medium">{{ t('extensions.rtsp.urlPreview') }}</span>
|
<span class="shrink-0 font-medium">{{ t('extensions.rtsp.urlPreview') }}</span>
|
||||||
<code class="truncate font-mono">{{ rtspStreamUrl }}</code>
|
<code class="truncate font-mono">{{ rtspStreamUrl }}</code>
|
||||||
</div>
|
</div>
|
||||||
<Button class="shrink-0" :disabled="loading || rtspLoading || rtspStatus?.service_status === 'running'" @click="saveRtspConfig">
|
<Button class="shrink-0" :disabled="loading || rtspLoading" @click="saveRtspConfig">
|
||||||
<Loader2 v-if="loading" class="h-4 w-4 mr-2 animate-spin" /><Check v-else-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ loading ? t('actionbar.applying') : saved ? t('common.success') : t('common.save') }}
|
<Loader2 v-if="loading" class="h-4 w-4 mr-2 animate-spin" /><Check v-else-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ loading ? t('actionbar.applying') : saved ? t('common.success') : t('common.save') }}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
@@ -4844,7 +4844,7 @@ watch(isWindows, () => {
|
|||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Label>{{ t('extensions.autoStart') }}</Label>
|
<Label>{{ t('extensions.autoStart') }}</Label>
|
||||||
<Switch v-model="vncLocalConfig.enabled" :disabled="vncStatus?.service_status === 'running'" />
|
<Switch v-model="vncLocalConfig.enabled" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
|
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
|
||||||
<Label class="sm:text-right">{{ t('extensions.vnc.bind') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.vnc.bind') }}</Label>
|
||||||
@@ -4903,7 +4903,7 @@ watch(isWindows, () => {
|
|||||||
<span class="shrink-0 font-medium">{{ t('extensions.vnc.urlPreview') }}</span>
|
<span class="shrink-0 font-medium">{{ t('extensions.vnc.urlPreview') }}</span>
|
||||||
<code class="truncate font-mono">{{ vncStreamUrl }}</code>
|
<code class="truncate font-mono">{{ vncStreamUrl }}</code>
|
||||||
</div>
|
</div>
|
||||||
<Button class="shrink-0" :disabled="loading || vncLoading || vncStatus?.service_status === 'running'" @click="saveVncConfig">
|
<Button class="shrink-0" :disabled="loading || vncLoading" @click="saveVncConfig">
|
||||||
<Loader2 v-if="loading" class="h-4 w-4 mr-2 animate-spin" /><Check v-else-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ loading ? t('actionbar.applying') : saved ? t('common.success') : t('common.save') }}
|
<Loader2 v-if="loading" class="h-4 w-4 mr-2 animate-spin" /><Check v-else-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ loading ? t('actionbar.applying') : saved ? t('common.success') : t('common.save') }}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
@@ -4969,7 +4969,7 @@ watch(isWindows, () => {
|
|||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Label>{{ t('extensions.autoStart') }}</Label>
|
<Label>{{ t('extensions.autoStart') }}</Label>
|
||||||
<Switch v-model="rustdeskLocalConfig.enabled" :disabled="rustdeskStatus?.service_status === 'running'" />
|
<Switch v-model="rustdeskLocalConfig.enabled" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
|
<div class="grid gap-2 sm:grid-cols-4 sm:items-center">
|
||||||
<Label class="sm:text-right">{{ t('extensions.rustdesk.codec') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rustdesk.codec') }}</Label>
|
||||||
@@ -5093,7 +5093,7 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter class="border-t pt-4 justify-end">
|
<CardFooter class="border-t pt-4 justify-end">
|
||||||
<Button :disabled="loading || rustdeskStatus?.service_status === 'running'" @click="saveRustdeskConfig">
|
<Button :disabled="loading || rustdeskLoading" @click="saveRustdeskConfig">
|
||||||
<Loader2 v-if="loading" class="h-4 w-4 mr-2 animate-spin" /><Check v-else-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ loading ? t('actionbar.applying') : saved ? t('common.success') : t('common.save') }}
|
<Loader2 v-if="loading" class="h-4 w-4 mr-2 animate-spin" /><Check v-else-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ loading ? t('actionbar.applying') : saved ? t('common.success') : t('common.save') }}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
|
|||||||
Reference in New Issue
Block a user