mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
fix: 修复 rtsp 和 RustDesk 扩展启停错误;修改部分参数描述文本
This commit is contained in:
@@ -88,9 +88,10 @@ impl RtspService {
|
|||||||
let status = self.status.clone();
|
let status = self.status.clone();
|
||||||
let client_handles = self.client_handles.clone();
|
let client_handles = self.client_handles.clone();
|
||||||
|
|
||||||
|
*self.status.write().await = RtspServiceStatus::Running;
|
||||||
|
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
tracing::info!("RTSP service listening on {}", bind_addr);
|
tracing::info!("RTSP service listening on {}", bind_addr);
|
||||||
*status.write().await = RtspServiceStatus::Running;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|||||||
@@ -53,3 +53,59 @@ pub async fn update_rtsp_config(
|
|||||||
|
|
||||||
Ok(Json(RtspConfigResponse::from(&new_config)))
|
Ok(Json(RtspConfigResponse::from(&new_config)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn start_rtsp_service(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> Result<Json<RtspStatusResponse>> {
|
||||||
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?;
|
||||||
|
let current_config = state.config.get().rtsp.clone();
|
||||||
|
let mut start_config = current_config.clone();
|
||||||
|
start_config.enabled = true;
|
||||||
|
|
||||||
|
apply_rtsp_config(
|
||||||
|
&state,
|
||||||
|
¤t_config,
|
||||||
|
&start_config,
|
||||||
|
ConfigApplyOptions::forced(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let status = {
|
||||||
|
let guard = state.rtsp.read().await;
|
||||||
|
if let Some(ref service) = *guard {
|
||||||
|
service.status().await
|
||||||
|
} else {
|
||||||
|
crate::rtsp::RtspServiceStatus::Stopped
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(RtspStatusResponse::new(¤t_config, status)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stop_rtsp_service(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> Result<Json<RtspStatusResponse>> {
|
||||||
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?;
|
||||||
|
let current_config = state.config.get().rtsp.clone();
|
||||||
|
let mut stop_config = current_config.clone();
|
||||||
|
stop_config.enabled = false;
|
||||||
|
|
||||||
|
apply_rtsp_config(
|
||||||
|
&state,
|
||||||
|
¤t_config,
|
||||||
|
&stop_config,
|
||||||
|
ConfigApplyOptions::forced(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let status = {
|
||||||
|
let guard = state.rtsp.read().await;
|
||||||
|
if let Some(ref service) = *guard {
|
||||||
|
service.status().await
|
||||||
|
} else {
|
||||||
|
crate::rtsp::RtspServiceStatus::Stopped
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(RtspStatusResponse::new(¤t_config, status)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub struct RustDeskConfigResponse {
|
|||||||
pub device_id: String,
|
pub device_id: String,
|
||||||
pub has_password: bool,
|
pub has_password: bool,
|
||||||
pub has_keypair: bool,
|
pub has_keypair: bool,
|
||||||
pub has_relay_key: bool,
|
pub relay_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&RustDeskConfig> for RustDeskConfigResponse {
|
impl From<&RustDeskConfig> for RustDeskConfigResponse {
|
||||||
@@ -28,7 +28,7 @@ impl From<&RustDeskConfig> for RustDeskConfigResponse {
|
|||||||
device_id: config.device_id.clone(),
|
device_id: config.device_id.clone(),
|
||||||
has_password: !config.device_password.is_empty(),
|
has_password: !config.device_password.is_empty(),
|
||||||
has_keypair: config.public_key.is_some() && config.private_key.is_some(),
|
has_keypair: config.public_key.is_some() && config.private_key.is_some(),
|
||||||
has_relay_key: config.relay_key.is_some(),
|
relay_key: config.relay_key.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,3 +144,71 @@ pub async fn get_device_password(State(state): State<Arc<AppState>>) -> Json<ser
|
|||||||
"device_password": config.device_password
|
"device_password": config.device_password
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn start_rustdesk_service(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> Result<Json<RustDeskStatusResponse>> {
|
||||||
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?;
|
||||||
|
let current_config = state.config.get().rustdesk.clone();
|
||||||
|
let mut start_config = current_config.clone();
|
||||||
|
start_config.enabled = true;
|
||||||
|
|
||||||
|
apply_rustdesk_config(
|
||||||
|
&state,
|
||||||
|
¤t_config,
|
||||||
|
&start_config,
|
||||||
|
ConfigApplyOptions::forced(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let (service_status, rendezvous_status) = {
|
||||||
|
let guard = state.rustdesk.read().await;
|
||||||
|
if let Some(ref service) = *guard {
|
||||||
|
let status = format!("{}", service.status());
|
||||||
|
let rv_status = service.rendezvous_status().map(|s| format!("{}", s));
|
||||||
|
(status, rv_status)
|
||||||
|
} else {
|
||||||
|
("not_initialized".to_string(), None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(RustDeskStatusResponse {
|
||||||
|
config: RustDeskConfigResponse::from(¤t_config),
|
||||||
|
service_status,
|
||||||
|
rendezvous_status,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stop_rustdesk_service(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> Result<Json<RustDeskStatusResponse>> {
|
||||||
|
let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?;
|
||||||
|
let current_config = state.config.get().rustdesk.clone();
|
||||||
|
let mut stop_config = current_config.clone();
|
||||||
|
stop_config.enabled = false;
|
||||||
|
|
||||||
|
apply_rustdesk_config(
|
||||||
|
&state,
|
||||||
|
¤t_config,
|
||||||
|
&stop_config,
|
||||||
|
ConfigApplyOptions::forced(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let (service_status, rendezvous_status) = {
|
||||||
|
let guard = state.rustdesk.read().await;
|
||||||
|
if let Some(ref service) = *guard {
|
||||||
|
let status = format!("{}", service.status());
|
||||||
|
let rv_status = service.rendezvous_status().map(|s| format!("{}", s));
|
||||||
|
(status, rv_status)
|
||||||
|
} else {
|
||||||
|
("not_initialized".to_string(), None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(RustDeskStatusResponse {
|
||||||
|
config: RustDeskConfigResponse::from(¤t_config),
|
||||||
|
service_status,
|
||||||
|
rendezvous_status,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ impl VideoConfigUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stream configuration response (includes has_turn_password)
|
/// Stream configuration response
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct StreamConfigResponse {
|
pub struct StreamConfigResponse {
|
||||||
@@ -104,8 +104,7 @@ pub struct StreamConfigResponse {
|
|||||||
pub stun_server: Option<String>,
|
pub stun_server: Option<String>,
|
||||||
pub turn_server: Option<String>,
|
pub turn_server: Option<String>,
|
||||||
pub turn_username: Option<String>,
|
pub turn_username: Option<String>,
|
||||||
/// Indicates whether TURN password has been configured (password is not returned)
|
pub turn_password: Option<String>,
|
||||||
pub has_turn_password: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&StreamConfig> for StreamConfigResponse {
|
impl From<&StreamConfig> for StreamConfigResponse {
|
||||||
@@ -120,7 +119,7 @@ impl From<&StreamConfig> for StreamConfigResponse {
|
|||||||
stun_server: config.stun_server.clone(),
|
stun_server: config.stun_server.clone(),
|
||||||
turn_server: config.turn_server.clone(),
|
turn_server: config.turn_server.clone(),
|
||||||
turn_username: config.turn_username.clone(),
|
turn_username: config.turn_username.clone(),
|
||||||
has_turn_password: config.turn_password.is_some(),
|
turn_password: config.turn_password.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,7 +797,7 @@ pub struct RtspConfigResponse {
|
|||||||
pub allow_one_client: bool,
|
pub allow_one_client: bool,
|
||||||
pub codec: RtspCodec,
|
pub codec: RtspCodec,
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
pub has_password: bool,
|
pub password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&RtspConfig> for RtspConfigResponse {
|
impl From<&RtspConfig> for RtspConfigResponse {
|
||||||
@@ -811,7 +810,7 @@ impl From<&RtspConfig> for RtspConfigResponse {
|
|||||||
allow_one_client: config.allow_one_client,
|
allow_one_client: config.allow_one_client,
|
||||||
codec: config.codec.clone(),
|
codec: config.codec.clone(),
|
||||||
username: config.username.clone(),
|
username: config.username.clone(),
|
||||||
has_password: config.password.is_some(),
|
password: config.password.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,14 @@ pub fn create_router(state: Arc<AppState>) -> Router {
|
|||||||
"/config/rustdesk/regenerate-password",
|
"/config/rustdesk/regenerate-password",
|
||||||
post(handlers::config::regenerate_device_password),
|
post(handlers::config::regenerate_device_password),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/config/rustdesk/start",
|
||||||
|
post(handlers::config::start_rustdesk_service),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/config/rustdesk/stop",
|
||||||
|
post(handlers::config::stop_rustdesk_service),
|
||||||
|
)
|
||||||
// RTSP configuration endpoints
|
// RTSP configuration endpoints
|
||||||
.route("/config/rtsp", get(handlers::config::get_rtsp_config))
|
.route("/config/rtsp", get(handlers::config::get_rtsp_config))
|
||||||
.route("/config/rtsp", patch(handlers::config::update_rtsp_config))
|
.route("/config/rtsp", patch(handlers::config::update_rtsp_config))
|
||||||
@@ -138,6 +146,14 @@ pub fn create_router(state: Arc<AppState>) -> Router {
|
|||||||
"/config/rtsp/status",
|
"/config/rtsp/status",
|
||||||
get(handlers::config::get_rtsp_status),
|
get(handlers::config::get_rtsp_status),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/config/rtsp/start",
|
||||||
|
post(handlers::config::start_rtsp_service),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/config/rtsp/stop",
|
||||||
|
post(handlers::config::stop_rtsp_service),
|
||||||
|
)
|
||||||
// Web server configuration
|
// Web server configuration
|
||||||
.route("/config/web", get(handlers::config::get_web_config))
|
.route("/config/web", get(handlers::config::get_web_config))
|
||||||
.route("/config/web", patch(handlers::config::update_web_config))
|
.route("/config/web", patch(handlers::config::update_web_config))
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
/**
|
|
||||||
* 配置管理 API - 域分离架构
|
|
||||||
*
|
|
||||||
* 每个配置域(video, stream, hid, msd, atx, audio)有独立的 GET/PATCH 端点,
|
|
||||||
* 避免配置项之间的相互干扰。
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AppConfig,
|
AppConfig,
|
||||||
AuthConfig,
|
AuthConfig,
|
||||||
@@ -38,22 +31,12 @@ import type {
|
|||||||
import { request } from './request'
|
import { request } from './request'
|
||||||
|
|
||||||
export const configApi = {
|
export const configApi = {
|
||||||
/**
|
|
||||||
* 获取完整配置
|
|
||||||
*/
|
|
||||||
getAll: () => request<AppConfig>('/config'),
|
getAll: () => request<AppConfig>('/config'),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authConfigApi = {
|
export const authConfigApi = {
|
||||||
/**
|
|
||||||
* 获取认证配置
|
|
||||||
*/
|
|
||||||
get: () => request<AuthConfig>('/config/auth'),
|
get: () => request<AuthConfig>('/config/auth'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新认证配置
|
|
||||||
* @param config 要更新的字段
|
|
||||||
*/
|
|
||||||
update: (config: AuthConfigUpdate) =>
|
update: (config: AuthConfigUpdate) =>
|
||||||
request<AuthConfig>('/config/auth', {
|
request<AuthConfig>('/config/auth', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -62,15 +45,8 @@ export const authConfigApi = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const videoConfigApi = {
|
export const videoConfigApi = {
|
||||||
/**
|
|
||||||
* 获取视频配置
|
|
||||||
*/
|
|
||||||
get: () => request<VideoConfig>('/config/video'),
|
get: () => request<VideoConfig>('/config/video'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新视频配置
|
|
||||||
* @param config 要更新的字段(仅发送需要修改的字段)
|
|
||||||
*/
|
|
||||||
update: (config: VideoConfigUpdate) =>
|
update: (config: VideoConfigUpdate) =>
|
||||||
request<VideoConfig>('/config/video', {
|
request<VideoConfig>('/config/video', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -79,15 +55,8 @@ export const videoConfigApi = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const streamConfigApi = {
|
export const streamConfigApi = {
|
||||||
/**
|
|
||||||
* 获取流配置
|
|
||||||
*/
|
|
||||||
get: () => request<StreamConfigResponse>('/config/stream'),
|
get: () => request<StreamConfigResponse>('/config/stream'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新流配置
|
|
||||||
* @param config 要更新的字段
|
|
||||||
*/
|
|
||||||
update: (config: StreamConfigUpdate) =>
|
update: (config: StreamConfigUpdate) =>
|
||||||
request<StreamConfigResponse>('/config/stream', {
|
request<StreamConfigResponse>('/config/stream', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -96,15 +65,8 @@ export const streamConfigApi = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const hidConfigApi = {
|
export const hidConfigApi = {
|
||||||
/**
|
|
||||||
* 获取 HID 配置
|
|
||||||
*/
|
|
||||||
get: () => request<HidConfig>('/config/hid'),
|
get: () => request<HidConfig>('/config/hid'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 HID 配置
|
|
||||||
* @param config 要更新的字段
|
|
||||||
*/
|
|
||||||
update: (config: HidConfigUpdate) =>
|
update: (config: HidConfigUpdate) =>
|
||||||
request<HidConfig>('/config/hid', {
|
request<HidConfig>('/config/hid', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -113,15 +75,8 @@ export const hidConfigApi = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const msdConfigApi = {
|
export const msdConfigApi = {
|
||||||
/**
|
|
||||||
* 获取 MSD 配置
|
|
||||||
*/
|
|
||||||
get: () => request<MsdConfig>('/config/msd'),
|
get: () => request<MsdConfig>('/config/msd'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 MSD 配置
|
|
||||||
* @param config 要更新的字段
|
|
||||||
*/
|
|
||||||
update: (config: MsdConfigUpdate) =>
|
update: (config: MsdConfigUpdate) =>
|
||||||
request<MsdConfig>('/config/msd', {
|
request<MsdConfig>('/config/msd', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -139,54 +94,29 @@ export interface WolHistoryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const atxConfigApi = {
|
export const atxConfigApi = {
|
||||||
/**
|
|
||||||
* 获取 ATX 配置
|
|
||||||
*/
|
|
||||||
get: () => request<AtxConfig>('/config/atx'),
|
get: () => request<AtxConfig>('/config/atx'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 ATX 配置
|
|
||||||
* @param config 要更新的字段
|
|
||||||
*/
|
|
||||||
update: (config: AtxConfigUpdate) =>
|
update: (config: AtxConfigUpdate) =>
|
||||||
request<AtxConfig>('/config/atx', {
|
request<AtxConfig>('/config/atx', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取可用的 ATX 设备(GPIO chips, USB relays)
|
|
||||||
*/
|
|
||||||
listDevices: () => request<AtxDevices>('/devices/atx'),
|
listDevices: () => request<AtxDevices>('/devices/atx'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送 Wake-on-LAN 魔术包
|
|
||||||
* @param macAddress 目标 MAC 地址
|
|
||||||
*/
|
|
||||||
sendWol: (macAddress: string) =>
|
sendWol: (macAddress: string) =>
|
||||||
request<{ success: boolean; message?: string }>('/atx/wol', {
|
request<{ success: boolean; message?: string }>('/atx/wol', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ mac_address: macAddress }),
|
body: JSON.stringify({ mac_address: macAddress }),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 WOL 历史记录(服务端持久化)
|
|
||||||
* @param limit 返回条数(1-50)
|
|
||||||
*/
|
|
||||||
getWolHistory: (limit = 5) =>
|
getWolHistory: (limit = 5) =>
|
||||||
request<WolHistoryResponse>(`/atx/wol/history?limit=${Math.max(1, Math.min(50, limit))}`),
|
request<WolHistoryResponse>(`/atx/wol/history?limit=${Math.max(1, Math.min(50, limit))}`),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const audioConfigApi = {
|
export const audioConfigApi = {
|
||||||
/**
|
|
||||||
* 获取音频配置
|
|
||||||
*/
|
|
||||||
get: () => request<AudioConfig>('/config/audio'),
|
get: () => request<AudioConfig>('/config/audio'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新音频配置
|
|
||||||
* @param config 要更新的字段
|
|
||||||
*/
|
|
||||||
update: (config: AudioConfigUpdate) =>
|
update: (config: AudioConfigUpdate) =>
|
||||||
request<AudioConfig>('/config/audio', {
|
request<AudioConfig>('/config/audio', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -195,59 +125,35 @@ export const audioConfigApi = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const extensionsApi = {
|
export const extensionsApi = {
|
||||||
/**
|
|
||||||
* 获取所有扩展状态
|
|
||||||
*/
|
|
||||||
getAll: () => request<ExtensionsStatus>('/extensions'),
|
getAll: () => request<ExtensionsStatus>('/extensions'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单个扩展状态
|
|
||||||
*/
|
|
||||||
get: (id: string) => request<ExtensionInfo>(`/extensions/${id}`),
|
get: (id: string) => request<ExtensionInfo>(`/extensions/${id}`),
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动扩展
|
|
||||||
*/
|
|
||||||
start: (id: string) =>
|
start: (id: string) =>
|
||||||
request<ExtensionInfo>(`/extensions/${id}/start`, {
|
request<ExtensionInfo>(`/extensions/${id}/start`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止扩展
|
|
||||||
*/
|
|
||||||
stop: (id: string) =>
|
stop: (id: string) =>
|
||||||
request<ExtensionInfo>(`/extensions/${id}/stop`, {
|
request<ExtensionInfo>(`/extensions/${id}/stop`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取扩展日志
|
|
||||||
*/
|
|
||||||
logs: (id: string, lines = 100) =>
|
logs: (id: string, lines = 100) =>
|
||||||
request<ExtensionLogs>(`/extensions/${id}/logs?lines=${lines}`),
|
request<ExtensionLogs>(`/extensions/${id}/logs?lines=${lines}`),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 ttyd 配置
|
|
||||||
*/
|
|
||||||
updateTtyd: (config: TtydConfigUpdate) =>
|
updateTtyd: (config: TtydConfigUpdate) =>
|
||||||
request<TtydConfig>('/extensions/ttyd/config', {
|
request<TtydConfig>('/extensions/ttyd/config', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 gostc 配置
|
|
||||||
*/
|
|
||||||
updateGostc: (config: GostcConfigUpdate) =>
|
updateGostc: (config: GostcConfigUpdate) =>
|
||||||
request<GostcConfig>('/extensions/gostc/config', {
|
request<GostcConfig>('/extensions/gostc/config', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 easytier 配置
|
|
||||||
*/
|
|
||||||
updateEasytier: (config: EasytierConfigUpdate) =>
|
updateEasytier: (config: EasytierConfigUpdate) =>
|
||||||
request<EasytierConfig>('/extensions/easytier/config', {
|
request<EasytierConfig>('/extensions/easytier/config', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -255,7 +161,6 @@ export const extensionsApi = {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RustDesk 配置响应 */
|
|
||||||
export interface RustDeskConfigResponse {
|
export interface RustDeskConfigResponse {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
rendezvous_server: string
|
rendezvous_server: string
|
||||||
@@ -263,17 +168,15 @@ export interface RustDeskConfigResponse {
|
|||||||
device_id: string
|
device_id: string
|
||||||
has_password: boolean
|
has_password: boolean
|
||||||
has_keypair: boolean
|
has_keypair: boolean
|
||||||
has_relay_key: boolean
|
relay_key: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RustDesk 状态响应 */
|
|
||||||
export interface RustDeskStatusResponse {
|
export interface RustDeskStatusResponse {
|
||||||
config: RustDeskConfigResponse
|
config: RustDeskConfigResponse
|
||||||
service_status: string
|
service_status: string
|
||||||
rendezvous_status: string | null
|
rendezvous_status: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RustDesk 配置更新 */
|
|
||||||
export interface RustDeskConfigUpdate {
|
export interface RustDeskConfigUpdate {
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
rendezvous_server?: string
|
rendezvous_server?: string
|
||||||
@@ -282,52 +185,37 @@ export interface RustDeskConfigUpdate {
|
|||||||
device_password?: string
|
device_password?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RustDesk 密码响应 */
|
|
||||||
export interface RustDeskPasswordResponse {
|
export interface RustDeskPasswordResponse {
|
||||||
device_id: string
|
device_id: string
|
||||||
device_password: string
|
device_password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rustdeskConfigApi = {
|
export const rustdeskConfigApi = {
|
||||||
/**
|
|
||||||
* 获取 RustDesk 配置
|
|
||||||
*/
|
|
||||||
get: () => request<RustDeskConfigResponse>('/config/rustdesk'),
|
get: () => request<RustDeskConfigResponse>('/config/rustdesk'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 RustDesk 配置
|
|
||||||
*/
|
|
||||||
update: (config: RustDeskConfigUpdate) =>
|
update: (config: RustDeskConfigUpdate) =>
|
||||||
request<RustDeskConfigResponse>('/config/rustdesk', {
|
request<RustDeskConfigResponse>('/config/rustdesk', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 RustDesk 完整状态
|
|
||||||
*/
|
|
||||||
getStatus: () => request<RustDeskStatusResponse>('/config/rustdesk/status'),
|
getStatus: () => request<RustDeskStatusResponse>('/config/rustdesk/status'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取设备密码(管理员专用)
|
|
||||||
*/
|
|
||||||
getPassword: () => request<RustDeskPasswordResponse>('/config/rustdesk/password'),
|
getPassword: () => request<RustDeskPasswordResponse>('/config/rustdesk/password'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新生成设备 ID
|
|
||||||
*/
|
|
||||||
regenerateId: () =>
|
regenerateId: () =>
|
||||||
request<RustDeskConfigResponse>('/config/rustdesk/regenerate-id', {
|
request<RustDeskConfigResponse>('/config/rustdesk/regenerate-id', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新生成设备密码
|
|
||||||
*/
|
|
||||||
regeneratePassword: () =>
|
regeneratePassword: () =>
|
||||||
request<RustDeskConfigResponse>('/config/rustdesk/regenerate-password', {
|
request<RustDeskConfigResponse>('/config/rustdesk/regenerate-password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
start: () => request<RustDeskStatusResponse>('/config/rustdesk/start', { method: 'POST' }),
|
||||||
|
|
||||||
|
stop: () => request<RustDeskStatusResponse>('/config/rustdesk/stop', { method: 'POST' }),
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RtspCodec = 'h264' | 'h265'
|
export type RtspCodec = 'h264' | 'h265'
|
||||||
@@ -340,7 +228,7 @@ export interface RtspConfigResponse {
|
|||||||
allow_one_client: boolean
|
allow_one_client: boolean
|
||||||
codec: RtspCodec
|
codec: RtspCodec
|
||||||
username?: string | null
|
username?: string | null
|
||||||
has_password: boolean
|
password: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RtspConfigUpdate {
|
export interface RtspConfigUpdate {
|
||||||
@@ -369,22 +257,19 @@ export const rtspConfigApi = {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
getStatus: () => request<RtspStatusResponse>('/config/rtsp/status'),
|
getStatus: () => request<RtspStatusResponse>('/config/rtsp/status'),
|
||||||
|
|
||||||
|
start: () => request<RtspStatusResponse>('/config/rtsp/start', { method: 'POST' }),
|
||||||
|
|
||||||
|
stop: () => request<RtspStatusResponse>('/config/rtsp/stop', { method: 'POST' }),
|
||||||
}
|
}
|
||||||
|
|
||||||
/** REST `/config/web` 响应(`WebConfigResponse` 别名,兼容旧命名) */
|
|
||||||
export type WebConfig = WebConfigResponse
|
export type WebConfig = WebConfigResponse
|
||||||
|
|
||||||
export type { WebConfigUpdate }
|
export type { WebConfigUpdate }
|
||||||
|
|
||||||
export const webConfigApi = {
|
export const webConfigApi = {
|
||||||
/**
|
|
||||||
* 获取 Web 服务器配置
|
|
||||||
*/
|
|
||||||
get: () => request<WebConfigResponse>('/config/web'),
|
get: () => request<WebConfigResponse>('/config/web'),
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 Web 服务器配置(含可选的证书上传)
|
|
||||||
*/
|
|
||||||
update: (config: WebConfigUpdate) =>
|
update: (config: WebConfigUpdate) =>
|
||||||
request<WebConfigResponse>('/config/web', {
|
request<WebConfigResponse>('/config/web', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -411,9 +296,6 @@ export const redfishConfigApi = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const systemApi = {
|
export const systemApi = {
|
||||||
/**
|
|
||||||
* 重启系统
|
|
||||||
*/
|
|
||||||
restart: () =>
|
restart: () =>
|
||||||
request<{ success: boolean; message?: string }>('/system/restart', {
|
request<{ success: boolean; message?: string }>('/system/restart', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -873,8 +873,7 @@ export default {
|
|||||||
turnServerHint: 'TURN relay server. Strongly recommended for public deployments or strict NAT environments.',
|
turnServerHint: 'TURN relay server. Strongly recommended for public deployments or strict NAT environments.',
|
||||||
turnUsername: 'TURN Username',
|
turnUsername: 'TURN Username',
|
||||||
turnPassword: 'TURN Password',
|
turnPassword: 'TURN Password',
|
||||||
turnPasswordConfigured: 'A password is already saved. Leave empty to keep the current password.',
|
turnCredentialsHint: 'Credentials used for TURN server authentication',
|
||||||
turnCredentialsHint: 'Credentials used for TURN server authentication',
|
|
||||||
iceConfigNote: 'Changes apply to the next WebRTC session',
|
iceConfigNote: 'Changes apply to the next WebRTC session',
|
||||||
},
|
},
|
||||||
virtualKeyboard: {
|
virtualKeyboard: {
|
||||||
@@ -994,15 +993,10 @@ export default {
|
|||||||
serverSettings: 'Server Settings',
|
serverSettings: 'Server Settings',
|
||||||
rendezvousServer: 'ID Server',
|
rendezvousServer: 'ID Server',
|
||||||
rendezvousServerPlaceholder: 'hbbs.example.com:21116',
|
rendezvousServerPlaceholder: 'hbbs.example.com:21116',
|
||||||
rendezvousServerHint: 'Configure your RustDesk server address (port optional, defaults to 21116)',
|
|
||||||
rendezvousServerRequired: 'Enter the RustDesk ID server',
|
rendezvousServerRequired: 'Enter the RustDesk ID server',
|
||||||
relayServer: 'Relay Server',
|
relayServer: 'Relay Server',
|
||||||
relayServerPlaceholder: 'hbbr.example.com:21117',
|
relayServerPlaceholder: 'hbbr.example.com:21117',
|
||||||
relayServerHint: 'Relay server address (port optional, defaults to 21117). Auto-derived if empty',
|
|
||||||
relayKey: 'Relay Key',
|
relayKey: 'Relay Key',
|
||||||
relayKeyPlaceholder: 'e.g. pLU0pEj2IZnNVKzrIO1pIdwGA3dOVJJLkFIYGOCGH1E=',
|
|
||||||
relayKeySet: 'Saved (32-byte Base64, usually 44 chars; leave empty and save to keep)',
|
|
||||||
relayKeyHint: 'Same as hbbs/hbbr -k: standard Base64 decoding to exactly 32 bytes (typically 44 characters including trailing =)',
|
|
||||||
deviceInfo: 'Device Info',
|
deviceInfo: 'Device Info',
|
||||||
deviceId: 'Device ID',
|
deviceId: 'Device ID',
|
||||||
deviceIdHint: 'Use this ID in RustDesk client to connect',
|
deviceIdHint: 'Use this ID in RustDesk client to connect',
|
||||||
@@ -1042,8 +1036,6 @@ export default {
|
|||||||
usernamePlaceholder: 'Empty means no authentication',
|
usernamePlaceholder: 'Empty means no authentication',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
passwordPlaceholder: 'Enter new password',
|
passwordPlaceholder: 'Enter new password',
|
||||||
passwordSet: '••••••••',
|
|
||||||
passwordHint: 'Leave empty to keep current password; enter a new value to update it.',
|
|
||||||
urlPreview: 'RTSP URL Preview',
|
urlPreview: 'RTSP URL Preview',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -719,7 +719,7 @@ export default {
|
|||||||
autoRecommended: '自动(推荐)',
|
autoRecommended: '自动(推荐)',
|
||||||
software: '软件',
|
software: '软件',
|
||||||
supportedFormats: '支持的编码格式',
|
supportedFormats: '支持的编码格式',
|
||||||
encoderHint: '硬件编码器延迟低、CPU 占用小;软件编码器兼容性更广但占用资源更多。',
|
encoderHint: '硬件编码器延迟和 CPU 占用比软件编码低,画质预设更好',
|
||||||
hidSettings: 'HID 设置',
|
hidSettings: 'HID 设置',
|
||||||
hidSettingsDesc: '配置键盘和鼠标控制',
|
hidSettingsDesc: '配置键盘和鼠标控制',
|
||||||
hidBackend: 'HID 后端',
|
hidBackend: 'HID 后端',
|
||||||
@@ -869,11 +869,10 @@ export default {
|
|||||||
stunServerHint: '留空将使用 Google 公共 STUN 服务器',
|
stunServerHint: '留空将使用 Google 公共 STUN 服务器',
|
||||||
turnServer: 'TURN 服务器',
|
turnServer: 'TURN 服务器',
|
||||||
turnServerPlaceholder: 'turn:turn.example.com:3478',
|
turnServerPlaceholder: 'turn:turn.example.com:3478',
|
||||||
turnServerHint: 'TURN 中继服务器;公网部署或严格 NAT 环境强烈建议配置',
|
turnServerHint: 'P2P 连接失败时进行流量中继',
|
||||||
turnUsername: 'TURN 用户名',
|
turnUsername: 'TURN 用户名',
|
||||||
turnPassword: 'TURN 密码',
|
turnPassword: 'TURN 密码',
|
||||||
turnPasswordConfigured: '密码已保存。留空则保持当前密码不变。',
|
turnCredentialsHint: '用于 TURN 服务器身份验证的凭据',
|
||||||
turnCredentialsHint: '用于 TURN 服务器身份验证的凭据',
|
|
||||||
iceConfigNote: '更改后将在下一次 WebRTC 会话建立时生效',
|
iceConfigNote: '更改后将在下一次 WebRTC 会话建立时生效',
|
||||||
},
|
},
|
||||||
virtualKeyboard: {
|
virtualKeyboard: {
|
||||||
@@ -993,15 +992,10 @@ export default {
|
|||||||
serverSettings: '服务器设置',
|
serverSettings: '服务器设置',
|
||||||
rendezvousServer: 'ID 服务器',
|
rendezvousServer: 'ID 服务器',
|
||||||
rendezvousServerPlaceholder: 'hbbs.example.com:21116',
|
rendezvousServerPlaceholder: 'hbbs.example.com:21116',
|
||||||
rendezvousServerHint: '请配置您的 RustDesk 服务器地址(端口可省略,默认 21116)',
|
|
||||||
rendezvousServerRequired: '请填写 RustDesk ID 服务器',
|
rendezvousServerRequired: '请填写 RustDesk ID 服务器',
|
||||||
relayServer: '中继服务器',
|
relayServer: '中继服务器',
|
||||||
relayServerPlaceholder: 'hbbr.example.com:21117',
|
relayServerPlaceholder: 'hbbr.example.com:21117',
|
||||||
relayServerHint: '中继服务器地址(端口可省略,默认 21117),留空则自动从 ID 服务器推导',
|
|
||||||
relayKey: '中继密钥',
|
relayKey: '中继密钥',
|
||||||
relayKeyPlaceholder: '例如 pLU0pEj2IZnNVKzrIO1pIdwGA3dOVJJLkFIYGOCGH1E=',
|
|
||||||
relayKeySet: '已保存(32 字节 Base64,通常 44 字符;留空保存则保留)',
|
|
||||||
relayKeyHint: '与 hbbs/hbbr 的 -k 一致:标准 Base64,解码后固定 32 字节(一般为 44 个字符,含末尾 =)',
|
|
||||||
deviceInfo: '设备信息',
|
deviceInfo: '设备信息',
|
||||||
deviceId: '设备 ID',
|
deviceId: '设备 ID',
|
||||||
deviceIdHint: '此 ID 用于 RustDesk 客户端连接',
|
deviceIdHint: '此 ID 用于 RustDesk 客户端连接',
|
||||||
@@ -1041,8 +1035,6 @@ export default {
|
|||||||
usernamePlaceholder: '留空表示无需认证',
|
usernamePlaceholder: '留空表示无需认证',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
passwordPlaceholder: '输入新密码',
|
passwordPlaceholder: '输入新密码',
|
||||||
passwordSet: '••••••••',
|
|
||||||
passwordHint: '留空表示保持当前密码;如需修改请输入新密码。',
|
|
||||||
urlPreview: 'RTSP 地址预览',
|
urlPreview: 'RTSP 地址预览',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,85 +2,51 @@
|
|||||||
Generated by typeshare 1.13.4
|
Generated by typeshare 1.13.4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Authentication configuration */
|
|
||||||
export interface AuthConfig {
|
export interface AuthConfig {
|
||||||
/** Session timeout in seconds */
|
|
||||||
session_timeout_secs: number;
|
session_timeout_secs: number;
|
||||||
/** Allow multiple concurrent web sessions (single-user mode) */
|
|
||||||
single_user_allow_multiple_sessions: boolean;
|
single_user_allow_multiple_sessions: boolean;
|
||||||
/** Enable 2FA */
|
|
||||||
totp_enabled: boolean;
|
totp_enabled: boolean;
|
||||||
/** TOTP secret (encrypted) */
|
|
||||||
totp_secret?: string;
|
totp_secret?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Video capture configuration */
|
|
||||||
export interface VideoConfig {
|
export interface VideoConfig {
|
||||||
/** Video device path (e.g., /dev/video0) */
|
|
||||||
device?: string;
|
device?: string;
|
||||||
/** Video pixel format (e.g., "MJPEG", "YUYV", "NV12") */
|
|
||||||
format?: string;
|
format?: string;
|
||||||
/** Resolution width */
|
|
||||||
width: number;
|
width: number;
|
||||||
/** Resolution height */
|
|
||||||
height: number;
|
height: number;
|
||||||
/** Frame rate */
|
|
||||||
fps: number;
|
fps: number;
|
||||||
/** JPEG quality (1-100) */
|
|
||||||
quality: number;
|
quality: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** HID backend type */
|
|
||||||
export enum HidBackend {
|
export enum HidBackend {
|
||||||
/** USB OTG HID gadget */
|
|
||||||
Otg = "otg",
|
Otg = "otg",
|
||||||
/** CH9329 serial HID controller */
|
|
||||||
Ch9329 = "ch9329",
|
Ch9329 = "ch9329",
|
||||||
/** Disabled */
|
|
||||||
None = "none",
|
None = "none",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** OTG USB device descriptor configuration */
|
|
||||||
export interface OtgDescriptorConfig {
|
export interface OtgDescriptorConfig {
|
||||||
/** USB Vendor ID (e.g., 0x1d6b) */
|
|
||||||
vendor_id: number;
|
vendor_id: number;
|
||||||
/** USB Product ID (e.g., 0x0104) */
|
|
||||||
product_id: number;
|
product_id: number;
|
||||||
/** Manufacturer string */
|
|
||||||
manufacturer: string;
|
manufacturer: string;
|
||||||
/** Product string */
|
|
||||||
product: string;
|
product: string;
|
||||||
/** Serial number (optional, auto-generated if not set) */
|
|
||||||
serial_number?: string;
|
serial_number?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** OTG HID function profile */
|
|
||||||
export enum OtgHidProfile {
|
export enum OtgHidProfile {
|
||||||
/** Full HID device set (keyboard + relative mouse + absolute mouse + consumer control) */
|
|
||||||
Full = "full",
|
Full = "full",
|
||||||
/** Full HID device set without consumer control */
|
|
||||||
FullNoConsumer = "full_no_consumer",
|
FullNoConsumer = "full_no_consumer",
|
||||||
/** Legacy profile: only keyboard */
|
|
||||||
LegacyKeyboard = "legacy_keyboard",
|
LegacyKeyboard = "legacy_keyboard",
|
||||||
/** Legacy profile: only relative mouse */
|
|
||||||
LegacyMouseRelative = "legacy_mouse_relative",
|
LegacyMouseRelative = "legacy_mouse_relative",
|
||||||
/** Custom function selection */
|
|
||||||
Custom = "custom",
|
Custom = "custom",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** OTG endpoint budget policy. */
|
|
||||||
export enum OtgEndpointBudget {
|
export enum OtgEndpointBudget {
|
||||||
/** Derive a safe default from the selected UDC. */
|
|
||||||
Auto = "auto",
|
Auto = "auto",
|
||||||
/** Limit OTG gadget functions to 5 endpoints. */
|
|
||||||
Five = "five",
|
Five = "five",
|
||||||
/** Limit OTG gadget functions to 6 endpoints. */
|
|
||||||
Six = "six",
|
Six = "six",
|
||||||
/** Do not impose a software endpoint budget. */
|
|
||||||
Unlimited = "unlimited",
|
Unlimited = "unlimited",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** OTG HID function selection (used when profile is Custom) */
|
|
||||||
export interface OtgHidFunctions {
|
export interface OtgHidFunctions {
|
||||||
keyboard: boolean;
|
keyboard: boolean;
|
||||||
mouse_relative: boolean;
|
mouse_relative: boolean;
|
||||||
@@ -88,226 +54,104 @@ export interface OtgHidFunctions {
|
|||||||
consumer: boolean;
|
consumer: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** HID configuration */
|
|
||||||
export interface HidConfig {
|
export interface HidConfig {
|
||||||
/** HID backend type */
|
|
||||||
backend: HidBackend;
|
backend: HidBackend;
|
||||||
/** OTG UDC (USB Device Controller) name */
|
|
||||||
otg_udc?: string;
|
otg_udc?: string;
|
||||||
/** OTG USB device descriptor configuration */
|
|
||||||
otg_descriptor?: OtgDescriptorConfig;
|
otg_descriptor?: OtgDescriptorConfig;
|
||||||
/** OTG HID function profile */
|
|
||||||
otg_profile?: OtgHidProfile;
|
otg_profile?: OtgHidProfile;
|
||||||
/** OTG endpoint budget policy */
|
|
||||||
otg_endpoint_budget?: OtgEndpointBudget;
|
otg_endpoint_budget?: OtgEndpointBudget;
|
||||||
/** OTG HID function selection (used when profile is Custom) */
|
|
||||||
otg_functions?: OtgHidFunctions;
|
otg_functions?: OtgHidFunctions;
|
||||||
/** Enable keyboard LED/status feedback for OTG keyboard */
|
|
||||||
otg_keyboard_leds?: boolean;
|
otg_keyboard_leds?: boolean;
|
||||||
/** CH9329 serial port */
|
|
||||||
ch9329_port: string;
|
ch9329_port: string;
|
||||||
/** CH9329 baud rate */
|
|
||||||
ch9329_baudrate: number;
|
ch9329_baudrate: number;
|
||||||
/** Mouse mode: absolute or relative */
|
|
||||||
mouse_absolute: boolean;
|
mouse_absolute: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** MSD configuration */
|
|
||||||
export interface MsdConfig {
|
export interface MsdConfig {
|
||||||
/** Enable MSD functionality */
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
/** MSD base directory (absolute path) */
|
|
||||||
msd_dir: string;
|
msd_dir: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Driver type for ATX key operations */
|
|
||||||
export enum AtxDriverType {
|
export enum AtxDriverType {
|
||||||
/** GPIO control via Linux character device */
|
|
||||||
Gpio = "gpio",
|
Gpio = "gpio",
|
||||||
/** USB HID relay module */
|
|
||||||
UsbRelay = "usbrelay",
|
UsbRelay = "usbrelay",
|
||||||
/** Serial/COM port relay (taobao LCUS type) */
|
|
||||||
Serial = "serial",
|
Serial = "serial",
|
||||||
/** Disabled / Not configured */
|
|
||||||
None = "none",
|
None = "none",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Active level for GPIO pins */
|
|
||||||
export enum ActiveLevel {
|
export enum ActiveLevel {
|
||||||
/** Active high (default for most cases) */
|
|
||||||
High = "high",
|
High = "high",
|
||||||
/** Active low (inverted) */
|
|
||||||
Low = "low",
|
Low = "low",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for a single ATX key (power or reset)
|
|
||||||
* This is the "four-tuple" configuration: (driver, device, pin/channel, level)
|
|
||||||
*/
|
|
||||||
export interface AtxKeyConfig {
|
export interface AtxKeyConfig {
|
||||||
/** Driver type (GPIO or USB Relay) */
|
|
||||||
driver: AtxDriverType;
|
driver: AtxDriverType;
|
||||||
/**
|
|
||||||
* Device path:
|
|
||||||
* - For GPIO: /dev/gpiochipX
|
|
||||||
* - For USB Relay: /dev/hidrawX
|
|
||||||
*/
|
|
||||||
device: string;
|
device: string;
|
||||||
/**
|
|
||||||
* Pin or channel number:
|
|
||||||
* - For GPIO: GPIO pin number
|
|
||||||
* - For USB Relay: relay channel (0-based)
|
|
||||||
* - For Serial Relay (LCUS): relay channel (1-based)
|
|
||||||
*/
|
|
||||||
pin: number;
|
pin: number;
|
||||||
/** Active level (only applicable to GPIO, ignored for USB Relay) */
|
|
||||||
active_level: ActiveLevel;
|
active_level: ActiveLevel;
|
||||||
/** Baud rate for serial relay (start with 9600) */
|
|
||||||
baud_rate: number;
|
baud_rate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** LED sensing configuration (optional) */
|
|
||||||
export interface AtxLedConfig {
|
export interface AtxLedConfig {
|
||||||
/** Whether LED sensing is enabled */
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
/** GPIO chip for LED sensing */
|
|
||||||
gpio_chip: string;
|
gpio_chip: string;
|
||||||
/** GPIO pin for LED input */
|
|
||||||
gpio_pin: number;
|
gpio_pin: number;
|
||||||
/** Whether LED is active low (inverted logic) */
|
|
||||||
inverted: boolean;
|
inverted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ATX power control configuration
|
|
||||||
*
|
|
||||||
* Each ATX action (power, reset) can be independently configured with its own
|
|
||||||
* hardware binding using the four-tuple: (driver, device, pin, active_level).
|
|
||||||
*/
|
|
||||||
export interface AtxConfig {
|
export interface AtxConfig {
|
||||||
/** Enable ATX functionality */
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
/** Power button configuration (used for both short and long press) */
|
|
||||||
power: AtxKeyConfig;
|
power: AtxKeyConfig;
|
||||||
/** Reset button configuration */
|
|
||||||
reset: AtxKeyConfig;
|
reset: AtxKeyConfig;
|
||||||
/** LED sensing configuration (optional) */
|
|
||||||
led: AtxLedConfig;
|
led: AtxLedConfig;
|
||||||
/** Network interface for WOL packets (empty = auto) */
|
|
||||||
wol_interface: string;
|
wol_interface: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Audio configuration
|
|
||||||
*
|
|
||||||
* Note: Sample rate is fixed at 48000Hz and channels at 2 (stereo).
|
|
||||||
* These are optimal for Opus encoding and match WebRTC requirements.
|
|
||||||
*/
|
|
||||||
export interface AudioConfig {
|
export interface AudioConfig {
|
||||||
/** Enable audio capture */
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
/** ALSA device name */
|
|
||||||
device: string;
|
device: string;
|
||||||
/** Audio quality preset: "voice", "balanced", "high" */
|
|
||||||
quality: string;
|
quality: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stream mode */
|
|
||||||
export enum StreamMode {
|
export enum StreamMode {
|
||||||
/** WebRTC with H264/H265 */
|
|
||||||
WebRTC = "webrtc",
|
WebRTC = "webrtc",
|
||||||
/** MJPEG over HTTP */
|
|
||||||
Mjpeg = "mjpeg",
|
Mjpeg = "mjpeg",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Encoder type */
|
|
||||||
export enum EncoderType {
|
export enum EncoderType {
|
||||||
/** Auto-detect best encoder */
|
|
||||||
Auto = "auto",
|
Auto = "auto",
|
||||||
/** Software encoder (libx264) */
|
|
||||||
Software = "software",
|
Software = "software",
|
||||||
/** VAAPI hardware encoder */
|
|
||||||
Vaapi = "vaapi",
|
Vaapi = "vaapi",
|
||||||
/** NVIDIA NVENC hardware encoder */
|
|
||||||
Nvenc = "nvenc",
|
Nvenc = "nvenc",
|
||||||
/** Intel Quick Sync hardware encoder */
|
|
||||||
Qsv = "qsv",
|
Qsv = "qsv",
|
||||||
/** AMD AMF hardware encoder */
|
|
||||||
Amf = "amf",
|
Amf = "amf",
|
||||||
/** Rockchip MPP hardware encoder */
|
|
||||||
Rkmpp = "rkmpp",
|
Rkmpp = "rkmpp",
|
||||||
/** V4L2 M2M hardware encoder */
|
|
||||||
V4l2m2m = "v4l2m2m",
|
V4l2m2m = "v4l2m2m",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bitrate preset for video encoding
|
|
||||||
*
|
|
||||||
* Simplifies bitrate configuration by providing three intuitive presets
|
|
||||||
* plus a custom option for advanced users.
|
|
||||||
*/
|
|
||||||
export type BitratePreset =
|
export type BitratePreset =
|
||||||
/**
|
|
||||||
* Speed priority: 1 Mbps, lowest latency, smaller GOP
|
|
||||||
* Best for: slow networks, remote management, low-bandwidth scenarios
|
|
||||||
*/
|
|
||||||
| { type: "Speed", value?: undefined }
|
| { type: "Speed", value?: undefined }
|
||||||
/**
|
|
||||||
* Balanced: 4 Mbps, good quality/latency tradeoff
|
|
||||||
* Best for: typical usage, recommended default
|
|
||||||
*/
|
|
||||||
| { type: "Balanced", value?: undefined }
|
| { type: "Balanced", value?: undefined }
|
||||||
/**
|
|
||||||
* Quality priority: 8 Mbps, best visual quality
|
|
||||||
* Best for: local network, high-bandwidth scenarios, detailed work
|
|
||||||
*/
|
|
||||||
| { type: "Quality", value?: undefined }
|
| { type: "Quality", value?: undefined }
|
||||||
/** Custom bitrate in kbps (for advanced users) */
|
|
||||||
| { type: "Custom", value: number };
|
| { type: "Custom", value: number };
|
||||||
|
|
||||||
/** Streaming configuration */
|
|
||||||
export interface StreamConfig {
|
export interface StreamConfig {
|
||||||
/** Stream mode */
|
|
||||||
mode: StreamMode;
|
mode: StreamMode;
|
||||||
/** Encoder type for H264/H265 */
|
|
||||||
encoder: EncoderType;
|
encoder: EncoderType;
|
||||||
/** Bitrate preset (Speed/Balanced/Quality) */
|
|
||||||
bitrate_preset: BitratePreset;
|
bitrate_preset: BitratePreset;
|
||||||
/**
|
|
||||||
* Custom STUN server (e.g., "stun:stun.l.google.com:19302")
|
|
||||||
* If empty, uses public ICE servers from secrets.toml
|
|
||||||
*/
|
|
||||||
stun_server?: string;
|
stun_server?: string;
|
||||||
/**
|
|
||||||
* Custom TURN server (e.g., "turn:turn.example.com:3478")
|
|
||||||
* If empty, uses public ICE servers from secrets.toml
|
|
||||||
*/
|
|
||||||
turn_server?: string;
|
turn_server?: string;
|
||||||
/** TURN username */
|
|
||||||
turn_username?: string;
|
turn_username?: string;
|
||||||
/** TURN password (stored encrypted in DB, not exposed via API) */
|
|
||||||
turn_password?: string;
|
turn_password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Web server configuration persisted in the database (includes on-disk TLS paths).
|
|
||||||
*
|
|
||||||
* The HTTP API for `/api/config/web` uses `WebConfigResponse` instead: no path fields, includes `has_custom_cert`.
|
|
||||||
*/
|
|
||||||
export interface WebConfig {
|
export interface WebConfig {
|
||||||
/** HTTP port */
|
|
||||||
http_port: number;
|
http_port: number;
|
||||||
/** HTTPS port */
|
|
||||||
https_port: number;
|
https_port: number;
|
||||||
/** Bind addresses (preferred) */
|
|
||||||
bind_addresses: string[];
|
bind_addresses: string[];
|
||||||
/** Bind address (legacy) */
|
|
||||||
bind_address: string;
|
bind_address: string;
|
||||||
/** Enable HTTPS */
|
|
||||||
https_enabled: boolean;
|
https_enabled: boolean;
|
||||||
/** Custom SSL certificate path */
|
|
||||||
ssl_cert_path?: string;
|
ssl_cert_path?: string;
|
||||||
/** Custom SSL key path */
|
|
||||||
ssl_key_path?: string;
|
ssl_key_path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,74 +181,46 @@ export interface ExtensionsConfig {
|
|||||||
easytier: EasytierConfig;
|
easytier: EasytierConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RustDesk configuration */
|
|
||||||
export interface RustDeskConfig {
|
export interface RustDeskConfig {
|
||||||
/** Enable RustDesk protocol */
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
/**
|
|
||||||
* Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100:21116"
|
|
||||||
* Required for RustDesk to function
|
|
||||||
*/
|
|
||||||
rendezvous_server: string;
|
rendezvous_server: string;
|
||||||
/**
|
|
||||||
* Relay server address (hbbr), if different from rendezvous server
|
|
||||||
* Usually the same host as rendezvous server but different port (21117)
|
|
||||||
*/
|
|
||||||
relay_server?: string;
|
relay_server?: string;
|
||||||
/** Device ID (9-digit number), auto-generated if empty */
|
|
||||||
device_id: string;
|
device_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RTSP output codec */
|
|
||||||
export enum RtspCodec {
|
export enum RtspCodec {
|
||||||
H264 = "h264",
|
H264 = "h264",
|
||||||
H265 = "h265",
|
H265 = "h265",
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RTSP configuration */
|
|
||||||
export interface RtspConfig {
|
export interface RtspConfig {
|
||||||
/** Enable RTSP output */
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
/** Bind IP address */
|
|
||||||
bind: string;
|
bind: string;
|
||||||
/** RTSP TCP listen port */
|
|
||||||
port: number;
|
port: number;
|
||||||
/** Stream path (without leading slash) */
|
|
||||||
path: string;
|
path: string;
|
||||||
/** Allow only one client connection at a time */
|
|
||||||
allow_one_client: boolean;
|
allow_one_client: boolean;
|
||||||
/** Output codec (H264/H265) */
|
|
||||||
codec: RtspCodec;
|
codec: RtspCodec;
|
||||||
/** Optional username for authentication */
|
|
||||||
username?: string;
|
username?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Main application configuration */
|
export interface RedfishConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
/** Whether initial setup has been completed */
|
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
/** Authentication settings */
|
|
||||||
auth: AuthConfig;
|
auth: AuthConfig;
|
||||||
/** Video capture settings */
|
|
||||||
video: VideoConfig;
|
video: VideoConfig;
|
||||||
/** HID (keyboard/mouse) settings */
|
|
||||||
hid: HidConfig;
|
hid: HidConfig;
|
||||||
/** Mass Storage Device settings */
|
|
||||||
msd: MsdConfig;
|
msd: MsdConfig;
|
||||||
/** ATX power control settings */
|
|
||||||
atx: AtxConfig;
|
atx: AtxConfig;
|
||||||
/** Audio settings */
|
|
||||||
audio: AudioConfig;
|
audio: AudioConfig;
|
||||||
/** Streaming settings */
|
|
||||||
stream: StreamConfig;
|
stream: StreamConfig;
|
||||||
/** Web server settings */
|
|
||||||
web: WebConfig;
|
web: WebConfig;
|
||||||
/** Extensions settings (ttyd, gostc, easytier) */
|
|
||||||
extensions: ExtensionsConfig;
|
extensions: ExtensionsConfig;
|
||||||
/** RustDesk remote access settings */
|
|
||||||
rustdesk: RustDeskConfig;
|
rustdesk: RustDeskConfig;
|
||||||
/** RTSP streaming settings */
|
|
||||||
rtsp: RtspConfig;
|
rtsp: RtspConfig;
|
||||||
|
redfish: RedfishConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update for a single ATX key configuration */
|
/** Update for a single ATX key configuration */
|
||||||
@@ -437,13 +253,9 @@ export interface AtxConfigUpdate {
|
|||||||
wol_interface?: string;
|
wol_interface?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Available ATX devices for discovery */
|
|
||||||
export interface AtxDevices {
|
export interface AtxDevices {
|
||||||
/** Available GPIO chips (/dev/gpiochip*) */
|
|
||||||
gpio_chips: string[];
|
gpio_chips: string[];
|
||||||
/** Available USB HID relay devices (/dev/hidraw*) */
|
|
||||||
usb_relays: string[];
|
usb_relays: string[];
|
||||||
/** Available Serial ports (/dev/ttyUSB*) */
|
|
||||||
serial_ports: string[];
|
serial_ports: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +269,6 @@ export interface AuthConfigUpdate {
|
|||||||
single_user_allow_multiple_sessions?: boolean;
|
single_user_allow_multiple_sessions?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update easytier config */
|
|
||||||
export interface EasytierConfigUpdate {
|
export interface EasytierConfigUpdate {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
network_name?: string;
|
network_name?: string;
|
||||||
@@ -471,9 +282,6 @@ export type ExtensionStatus =
|
|||||||
| { state: "stopped", data?: undefined }
|
| { state: "stopped", data?: undefined }
|
||||||
| { state: "running", data: {
|
| { state: "running", data: {
|
||||||
pid: number;
|
pid: number;
|
||||||
}}
|
|
||||||
| { state: "failed", data: {
|
|
||||||
error: string;
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
export interface EasytierInfo {
|
export interface EasytierInfo {
|
||||||
@@ -516,7 +324,6 @@ export interface ExtensionsStatus {
|
|||||||
easytier: EasytierInfo;
|
easytier: EasytierInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update gostc config */
|
|
||||||
export interface GostcConfigUpdate {
|
export interface GostcConfigUpdate {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
addr?: string;
|
addr?: string;
|
||||||
@@ -566,7 +373,7 @@ export interface RtspConfigResponse {
|
|||||||
allow_one_client: boolean;
|
allow_one_client: boolean;
|
||||||
codec: RtspCodec;
|
codec: RtspCodec;
|
||||||
username?: string;
|
username?: string;
|
||||||
has_password: boolean;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RtspConfigUpdate {
|
export interface RtspConfigUpdate {
|
||||||
@@ -593,7 +400,7 @@ export interface RustDeskConfigUpdate {
|
|||||||
device_password?: string;
|
device_password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stream configuration response (includes has_turn_password) */
|
/** Stream configuration response */
|
||||||
export interface StreamConfigResponse {
|
export interface StreamConfigResponse {
|
||||||
mode: StreamMode;
|
mode: StreamMode;
|
||||||
encoder: EncoderType;
|
encoder: EncoderType;
|
||||||
@@ -605,8 +412,7 @@ export interface StreamConfigResponse {
|
|||||||
stun_server?: string;
|
stun_server?: string;
|
||||||
turn_server?: string;
|
turn_server?: string;
|
||||||
turn_username?: string;
|
turn_username?: string;
|
||||||
/** Indicates whether TURN password has been configured (password is not returned) */
|
turn_password?: string;
|
||||||
has_turn_password: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StreamConfigUpdate {
|
export interface StreamConfigUpdate {
|
||||||
@@ -629,7 +435,6 @@ export interface StreamConfigUpdate {
|
|||||||
turn_password?: string;
|
turn_password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update ttyd config */
|
|
||||||
export interface TtydConfigUpdate {
|
export interface TtydConfigUpdate {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
shell?: string;
|
shell?: string;
|
||||||
@@ -673,12 +478,6 @@ export interface WebConfigUpdate {
|
|||||||
clear_custom_cert?: boolean;
|
clear_custom_cert?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shared canonical keyboard key identifiers used across frontend and backend.
|
|
||||||
*
|
|
||||||
* The enum names intentionally mirror `KeyboardEvent.code` style values so the
|
|
||||||
* browser, virtual keyboard, and HID backend can all speak the same language.
|
|
||||||
*/
|
|
||||||
export enum CanonicalKey {
|
export enum CanonicalKey {
|
||||||
KeyA = "KeyA",
|
KeyA = "KeyA",
|
||||||
KeyB = "KeyB",
|
KeyB = "KeyB",
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
atxConfigApi,
|
atxConfigApi,
|
||||||
extensionsApi,
|
extensionsApi,
|
||||||
redfishConfigApi,
|
redfishConfigApi,
|
||||||
|
rtspConfigApi,
|
||||||
|
rustdeskConfigApi,
|
||||||
systemApi,
|
systemApi,
|
||||||
updateApi,
|
updateApi,
|
||||||
usbApi,
|
usbApi,
|
||||||
@@ -295,7 +297,6 @@ const passwordSaving = ref(false)
|
|||||||
const passwordSaved = ref(false)
|
const passwordSaved = ref(false)
|
||||||
const passwordError = ref('')
|
const passwordError = ref('')
|
||||||
const showPasswords = ref(false)
|
const showPasswords = ref(false)
|
||||||
|
|
||||||
const authConfig = ref<AuthConfig>({
|
const authConfig = ref<AuthConfig>({
|
||||||
session_timeout_secs: 3600 * 24,
|
session_timeout_secs: 3600 * 24,
|
||||||
single_user_allow_multiple_sessions: false,
|
single_user_allow_multiple_sessions: false,
|
||||||
@@ -526,8 +527,6 @@ const config = ref({
|
|||||||
turn_password: '',
|
turn_password: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasTurnPassword = ref(false)
|
|
||||||
|
|
||||||
type OtgSelfCheckLevel = 'info' | 'warn' | 'error'
|
type OtgSelfCheckLevel = 'info' | 'warn' | 'error'
|
||||||
type OtgCheckGroupStatus = 'ok' | 'warn' | 'error' | 'skipped'
|
type OtgCheckGroupStatus = 'ok' | 'warn' | 'error' | 'skipped'
|
||||||
|
|
||||||
@@ -1206,16 +1205,12 @@ async function saveConfig() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (activeSection.value === 'video') {
|
if (activeSection.value === 'video') {
|
||||||
const turnUrl = config.value.turn_server.trim()
|
|
||||||
await configStore.updateStream({
|
await configStore.updateStream({
|
||||||
encoder: config.value.encoder_backend as any,
|
encoder: config.value.encoder_backend as any,
|
||||||
stun_server: config.value.stun_server.trim(),
|
stun_server: config.value.stun_server.trim(),
|
||||||
turn_server: turnUrl,
|
turn_server: config.value.turn_server.trim(),
|
||||||
turn_username: config.value.turn_username.trim(),
|
turn_username: config.value.turn_username.trim(),
|
||||||
turn_password:
|
turn_password: config.value.turn_password.trim(),
|
||||||
turnUrl === ''
|
|
||||||
? ''
|
|
||||||
: config.value.turn_password || undefined,
|
|
||||||
})
|
})
|
||||||
await configStore.updateVideo({
|
await configStore.updateVideo({
|
||||||
device: config.value.video_device || undefined,
|
device: config.value.video_device || undefined,
|
||||||
@@ -1303,11 +1298,9 @@ async function loadConfig() {
|
|||||||
stun_server: stream.stun_server || '',
|
stun_server: stream.stun_server || '',
|
||||||
turn_server: stream.turn_server || '',
|
turn_server: stream.turn_server || '',
|
||||||
turn_username: stream.turn_username || '',
|
turn_username: stream.turn_username || '',
|
||||||
turn_password: '', // Password is never returned from server; set-only field
|
turn_password: stream.turn_password || '',
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTurnPassword.value = stream.has_turn_password || false
|
|
||||||
|
|
||||||
if (hid.otg_descriptor) {
|
if (hid.otg_descriptor) {
|
||||||
otgVendorIdHex.value = hid.otg_descriptor.vendor_id?.toString(16).padStart(4, '0') || '1d6b'
|
otgVendorIdHex.value = hid.otg_descriptor.vendor_id?.toString(16).padStart(4, '0') || '1d6b'
|
||||||
otgProductIdHex.value = hid.otg_descriptor.product_id?.toString(16).padStart(4, '0') || '0104'
|
otgProductIdHex.value = hid.otg_descriptor.product_id?.toString(16).padStart(4, '0') || '0104'
|
||||||
@@ -1448,7 +1441,6 @@ function getExtStatusText(status: ExtensionStatus | undefined): string {
|
|||||||
case 'unavailable': return t('extensions.unavailable')
|
case 'unavailable': return t('extensions.unavailable')
|
||||||
case 'stopped': return t('extensions.stopped')
|
case 'stopped': return t('extensions.stopped')
|
||||||
case 'running': return t('extensions.running')
|
case 'running': return t('extensions.running')
|
||||||
case 'failed': return t('extensions.failed')
|
|
||||||
default: return t('extensions.stopped')
|
default: return t('extensions.stopped')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1459,7 +1451,6 @@ function getExtStatusClass(status: ExtensionStatus | undefined): string {
|
|||||||
case 'unavailable': return 'bg-gray-400'
|
case 'unavailable': return 'bg-gray-400'
|
||||||
case 'stopped': return 'bg-gray-400'
|
case 'stopped': return 'bg-gray-400'
|
||||||
case 'running': return 'bg-green-500'
|
case 'running': return 'bg-green-500'
|
||||||
case 'failed': return 'bg-red-500'
|
|
||||||
default: return 'bg-gray-400'
|
default: return 'bg-gray-400'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1616,19 +1607,23 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function applyRustdeskStatus(status: RustDeskStatusResponse) {
|
||||||
|
const config = status.config
|
||||||
|
rustdeskConfig.value = config
|
||||||
|
rustdeskStatus.value = status
|
||||||
|
rustdeskLocalConfig.value = {
|
||||||
|
enabled: config.enabled,
|
||||||
|
rendezvous_server: config.rendezvous_server,
|
||||||
|
relay_server: config.relay_server || '',
|
||||||
|
relay_key: config.relay_key || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadRustdeskConfig() {
|
async function loadRustdeskConfig() {
|
||||||
rustdeskLoading.value = true
|
rustdeskLoading.value = true
|
||||||
try {
|
try {
|
||||||
const status = await configStore.refreshRustdeskStatus()
|
const status = await configStore.refreshRustdeskStatus()
|
||||||
const config = status.config
|
applyRustdeskStatus(status)
|
||||||
rustdeskConfig.value = config
|
|
||||||
rustdeskStatus.value = status
|
|
||||||
rustdeskLocalConfig.value = {
|
|
||||||
enabled: config.enabled,
|
|
||||||
rendezvous_server: config.rendezvous_server,
|
|
||||||
relay_server: config.relay_server || '',
|
|
||||||
relay_key: '',
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
rustdeskLoading.value = false
|
rustdeskLoading.value = false
|
||||||
@@ -1642,17 +1637,17 @@ async function loadRustdeskPassword() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeRustdeskServer(value: string, defaultPort: number): string | undefined {
|
function normalizeRustdeskServer(value: string, defaultPort: number): string {
|
||||||
const trimmed = value.trim()
|
const trimmed = value.trim()
|
||||||
if (!trimmed) return undefined
|
if (!trimmed) return ''
|
||||||
if (trimmed.includes(':')) return trimmed
|
if (trimmed.includes(':')) return trimmed
|
||||||
return `${trimmed}:${defaultPort}`
|
return `${trimmed}:${defaultPort}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Strip line breaks from pasted keys; empty means “do not change” on PATCH. */
|
/** Strip line breaks from pasted keys. */
|
||||||
function normalizeRustdeskRelayKey(value: string): string | undefined {
|
function normalizeRustdeskRelayKey(value: string): string {
|
||||||
const cleaned = value.replace(/\r?\n/g, '').trim()
|
const cleaned = value.replace(/\r?\n/g, '').trim()
|
||||||
return cleaned || undefined
|
return cleaned
|
||||||
}
|
}
|
||||||
|
|
||||||
function showValidationError(message: string): boolean {
|
function showValidationError(message: string): boolean {
|
||||||
@@ -2002,7 +1997,6 @@ async function saveRustdeskConfig() {
|
|||||||
relay_key: normalizeRustdeskRelayKey(rustdeskLocalConfig.value.relay_key),
|
relay_key: normalizeRustdeskRelayKey(rustdeskLocalConfig.value.relay_key),
|
||||||
})
|
})
|
||||||
await loadRustdeskConfig()
|
await loadRustdeskConfig()
|
||||||
rustdeskLocalConfig.value.relay_key = ''
|
|
||||||
saved.value = true
|
saved.value = true
|
||||||
setTimeout(() => (saved.value = false), 2000)
|
setTimeout(() => (saved.value = false), 2000)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -2042,9 +2036,8 @@ async function startRustdesk() {
|
|||||||
|
|
||||||
rustdeskLoading.value = true
|
rustdeskLoading.value = true
|
||||||
try {
|
try {
|
||||||
await configStore.updateRustdesk({ enabled: true })
|
const status = await rustdeskConfigApi.start()
|
||||||
rustdeskLocalConfig.value.enabled = true
|
applyRustdeskStatus(status)
|
||||||
await loadRustdeskConfig()
|
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
rustdeskLoading.value = false
|
rustdeskLoading.value = false
|
||||||
@@ -2054,9 +2047,8 @@ async function startRustdesk() {
|
|||||||
async function stopRustdesk() {
|
async function stopRustdesk() {
|
||||||
rustdeskLoading.value = true
|
rustdeskLoading.value = true
|
||||||
try {
|
try {
|
||||||
await configStore.updateRustdesk({ enabled: false })
|
const status = await rustdeskConfigApi.stop()
|
||||||
rustdeskLocalConfig.value.enabled = false
|
applyRustdeskStatus(status)
|
||||||
await loadRustdeskConfig()
|
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
rustdeskLoading.value = false
|
rustdeskLoading.value = false
|
||||||
@@ -2113,21 +2105,25 @@ function getRustdeskStatusClass(status: string | null | undefined): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyRtspStatus(status: RtspStatusResponse) {
|
||||||
|
rtspStatus.value = status
|
||||||
|
rtspLocalConfig.value = {
|
||||||
|
enabled: status.config.enabled,
|
||||||
|
bind: status.config.bind,
|
||||||
|
port: status.config.port,
|
||||||
|
path: status.config.path,
|
||||||
|
allow_one_client: status.config.allow_one_client,
|
||||||
|
codec: status.config.codec,
|
||||||
|
username: status.config.username || '',
|
||||||
|
password: status.config.password || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadRtspConfig() {
|
async function loadRtspConfig() {
|
||||||
rtspLoading.value = true
|
rtspLoading.value = true
|
||||||
try {
|
try {
|
||||||
const status = await configStore.refreshRtspStatus()
|
const status = await configStore.refreshRtspStatus()
|
||||||
rtspStatus.value = status
|
applyRtspStatus(status)
|
||||||
rtspLocalConfig.value = {
|
|
||||||
enabled: status.config.enabled,
|
|
||||||
bind: status.config.bind,
|
|
||||||
port: status.config.port,
|
|
||||||
path: status.config.path,
|
|
||||||
allow_one_client: status.config.allow_one_client,
|
|
||||||
codec: status.config.codec,
|
|
||||||
username: status.config.username || '',
|
|
||||||
password: '',
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
rtspLoading.value = false
|
rtspLoading.value = false
|
||||||
@@ -2148,14 +2144,10 @@ async function saveRtspConfig() {
|
|||||||
username: (rtspLocalConfig.value.username || '').trim(),
|
username: (rtspLocalConfig.value.username || '').trim(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPassword = (rtspLocalConfig.value.password || '').trim()
|
update.password = (rtspLocalConfig.value.password || '').trim()
|
||||||
if (nextPassword) {
|
|
||||||
update.password = nextPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
await configStore.updateRtsp(update)
|
await configStore.updateRtsp(update)
|
||||||
await loadRtspConfig()
|
await loadRtspConfig()
|
||||||
rtspLocalConfig.value.password = ''
|
|
||||||
saved.value = true
|
saved.value = true
|
||||||
setTimeout(() => (saved.value = false), 2000)
|
setTimeout(() => (saved.value = false), 2000)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -2167,9 +2159,8 @@ async function saveRtspConfig() {
|
|||||||
async function startRtsp() {
|
async function startRtsp() {
|
||||||
rtspLoading.value = true
|
rtspLoading.value = true
|
||||||
try {
|
try {
|
||||||
await configStore.updateRtsp({ enabled: true })
|
const status = await rtspConfigApi.start()
|
||||||
rtspLocalConfig.value.enabled = true
|
applyRtspStatus(status)
|
||||||
await loadRtspConfig()
|
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
rtspLoading.value = false
|
rtspLoading.value = false
|
||||||
@@ -2179,9 +2170,8 @@ async function startRtsp() {
|
|||||||
async function stopRtsp() {
|
async function stopRtsp() {
|
||||||
rtspLoading.value = true
|
rtspLoading.value = true
|
||||||
try {
|
try {
|
||||||
await configStore.updateRtsp({ enabled: false })
|
const status = await rtspConfigApi.stop()
|
||||||
rtspLocalConfig.value.enabled = false
|
applyRtspStatus(status)
|
||||||
await loadRtspConfig()
|
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
rtspLoading.value = false
|
rtspLoading.value = false
|
||||||
@@ -2573,7 +2563,7 @@ watch(isWindows, () => {
|
|||||||
<Input
|
<Input
|
||||||
id="turn-username"
|
id="turn-username"
|
||||||
v-model="config.turn_username"
|
v-model="config.turn_username"
|
||||||
:disabled="!config.turn_server"
|
:disabled="!config.stun_server && !config.turn_server"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
@@ -2583,8 +2573,7 @@ watch(isWindows, () => {
|
|||||||
id="turn-password"
|
id="turn-password"
|
||||||
v-model="config.turn_password"
|
v-model="config.turn_password"
|
||||||
:type="showPasswords ? 'text' : 'password'"
|
:type="showPasswords ? 'text' : 'password'"
|
||||||
:disabled="!config.turn_server"
|
:disabled="!config.stun_server && !config.turn_server"
|
||||||
:placeholder="hasTurnPassword ? '••••••••' : ''"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2596,7 +2585,6 @@ watch(isWindows, () => {
|
|||||||
<EyeOff v-else class="h-4 w-4" />
|
<EyeOff v-else class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="hasTurnPassword && !config.turn_password" class="text-xs text-muted-foreground">{{ t('settings.turnPasswordConfigured') }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-muted-foreground">{{ t('settings.turnCredentialsHint') }}</p>
|
<p class="text-xs text-muted-foreground">{{ t('settings.turnCredentialsHint') }}</p>
|
||||||
@@ -4007,10 +3995,10 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
v-if="rtspStatus?.service_status !== 'running'"
|
v-if="rtspStatus?.service_status !== 'running' && rtspStatus?.service_status !== 'starting'"
|
||||||
size="sm"
|
size="sm"
|
||||||
@click="startRtsp"
|
@click="startRtsp"
|
||||||
:disabled="rtspLoading"
|
:disabled="rtspLoading || rtspStatus?.service_status === 'starting'"
|
||||||
>
|
>
|
||||||
<Play class="h-4 w-4 mr-1" />
|
<Play class="h-4 w-4 mr-1" />
|
||||||
{{ t('extensions.start') }}
|
{{ t('extensions.start') }}
|
||||||
@@ -4032,27 +4020,27 @@ 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" />
|
<Switch v-model="rtspLocalConfig.enabled" :disabled="rtspStatus?.service_status === 'running'" />
|
||||||
</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>
|
||||||
<Input v-model="rtspLocalConfig.bind" class="sm:col-span-3" placeholder="0.0.0.0" />
|
<Input v-model="rtspLocalConfig.bind" class="sm:col-span-3" placeholder="0.0.0.0" :disabled="rtspStatus?.service_status === 'running'" />
|
||||||
</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.port') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rtsp.port') }}</Label>
|
||||||
<Input v-model.number="rtspLocalConfig.port" class="sm:col-span-3" type="number" min="1" max="65535" />
|
<Input v-model.number="rtspLocalConfig.port" class="sm:col-span-3" type="number" min="1" max="65535" :disabled="rtspStatus?.service_status === 'running'" />
|
||||||
</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.path') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rtsp.path') }}</Label>
|
||||||
<div class="sm:col-span-3 space-y-1">
|
<div class="sm:col-span-3 space-y-1">
|
||||||
<Input v-model="rtspLocalConfig.path" :placeholder="t('extensions.rtsp.pathPlaceholder')" />
|
<Input v-model="rtspLocalConfig.path" :placeholder="t('extensions.rtsp.pathPlaceholder')" :disabled="rtspStatus?.service_status === 'running'" />
|
||||||
<p class="text-xs text-muted-foreground">{{ t('extensions.rtsp.pathHint') }}</p>
|
<p class="text-xs text-muted-foreground">{{ t('extensions.rtsp.pathHint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</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.codec') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rtsp.codec') }}</Label>
|
||||||
<div class="sm:col-span-3 space-y-1">
|
<div class="sm:col-span-3 space-y-1">
|
||||||
<select v-model="rtspLocalConfig.codec" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
|
<select v-model="rtspLocalConfig.codec" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm" :disabled="rtspStatus?.service_status === 'running'">
|
||||||
<option value="h264">H.264</option>
|
<option value="h264">H.264</option>
|
||||||
<option value="h265">H.265</option>
|
<option value="h265">H.265</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -4061,21 +4049,32 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Label>{{ t('extensions.rtsp.allowOneClient') }}</Label>
|
<Label>{{ t('extensions.rtsp.allowOneClient') }}</Label>
|
||||||
<Switch v-model="rtspLocalConfig.allow_one_client" />
|
<Switch v-model="rtspLocalConfig.allow_one_client" :disabled="rtspStatus?.service_status === 'running'" />
|
||||||
</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.username') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rtsp.username') }}</Label>
|
||||||
<Input v-model="rtspLocalConfig.username" class="sm:col-span-3" :placeholder="t('extensions.rtsp.usernamePlaceholder')" />
|
<Input v-model="rtspLocalConfig.username" class="sm:col-span-3" :placeholder="t('extensions.rtsp.usernamePlaceholder')" :disabled="rtspStatus?.service_status === 'running'" />
|
||||||
</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.password') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rtsp.password') }}</Label>
|
||||||
<div class="sm:col-span-3 space-y-1">
|
<div class="sm:col-span-3 space-y-1">
|
||||||
<Input
|
<div class="relative">
|
||||||
v-model="rtspLocalConfig.password"
|
<Input
|
||||||
type="password"
|
v-model="rtspLocalConfig.password"
|
||||||
:placeholder="rtspStatus?.config?.has_password ? t('extensions.rtsp.passwordSet') : t('extensions.rtsp.passwordPlaceholder')"
|
:type="showPasswords ? 'text' : 'password'"
|
||||||
/>
|
:placeholder="t('extensions.rtsp.passwordPlaceholder')"
|
||||||
<p class="text-xs text-muted-foreground">{{ t('extensions.rtsp.passwordHint') }}</p>
|
:disabled="rtspStatus?.service_status === 'running'"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||||
|
:aria-label="showPasswords ? t('extensions.rustdesk.hidePassword') : t('extensions.rustdesk.showPassword')"
|
||||||
|
@click="showPasswords = !showPasswords"
|
||||||
|
>
|
||||||
|
<Eye v-if="!showPasswords" class="h-4 w-4" />
|
||||||
|
<EyeOff v-else class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4089,7 +4088,7 @@ watch(isWindows, () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<Button :disabled="loading || rtspLoading" @click="saveRtspConfig">
|
<Button :disabled="loading || rtspLoading || rtspStatus?.service_status === 'running'" @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>
|
||||||
</div>
|
</div>
|
||||||
@@ -4154,7 +4153,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" />
|
<Switch v-model="rustdeskLocalConfig.enabled" :disabled="rustdeskStatus?.service_status === 'running'" />
|
||||||
</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.rendezvousServer') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rustdesk.rendezvousServer') }}</Label>
|
||||||
@@ -4162,8 +4161,8 @@ watch(isWindows, () => {
|
|||||||
<Input
|
<Input
|
||||||
v-model="rustdeskLocalConfig.rendezvous_server"
|
v-model="rustdeskLocalConfig.rendezvous_server"
|
||||||
:placeholder="t('extensions.rustdesk.rendezvousServerPlaceholder')"
|
:placeholder="t('extensions.rustdesk.rendezvousServerPlaceholder')"
|
||||||
|
:disabled="rustdeskStatus?.service_status === 'running'"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.rendezvousServerHint') }}</p>
|
|
||||||
<p v-if="rustdeskLocalConfig.enabled && rustdeskValidationMessage" class="text-xs text-destructive">{{ rustdeskValidationMessage }}</p>
|
<p v-if="rustdeskLocalConfig.enabled && rustdeskValidationMessage" class="text-xs text-destructive">{{ rustdeskValidationMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4173,23 +4172,33 @@ watch(isWindows, () => {
|
|||||||
<Input
|
<Input
|
||||||
v-model="rustdeskLocalConfig.relay_server"
|
v-model="rustdeskLocalConfig.relay_server"
|
||||||
:placeholder="t('extensions.rustdesk.relayServerPlaceholder')"
|
:placeholder="t('extensions.rustdesk.relayServerPlaceholder')"
|
||||||
|
:disabled="!rustdeskLocalConfig.rendezvous_server || rustdeskStatus?.service_status === 'running'"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.relayServerHint') }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</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.relayKey') }}</Label>
|
<Label class="sm:text-right">{{ t('extensions.rustdesk.relayKey') }}</Label>
|
||||||
<div class="sm:col-span-3 space-y-1">
|
<div class="sm:col-span-3 space-y-1">
|
||||||
<Input
|
<div class="relative">
|
||||||
v-model="rustdeskLocalConfig.relay_key"
|
<Input
|
||||||
type="text"
|
v-model="rustdeskLocalConfig.relay_key"
|
||||||
maxlength="44"
|
:type="showPasswords ? 'text' : 'password'"
|
||||||
autocomplete="off"
|
:disabled="!rustdeskLocalConfig.rendezvous_server || rustdeskStatus?.service_status === 'running'"
|
||||||
spellcheck="false"
|
maxlength="44"
|
||||||
class="font-mono"
|
autocomplete="off"
|
||||||
:placeholder="rustdeskStatus?.config?.has_relay_key ? t('extensions.rustdesk.relayKeySet') : t('extensions.rustdesk.relayKeyPlaceholder')"
|
spellcheck="false"
|
||||||
/>
|
class="font-mono"
|
||||||
<p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.relayKeyHint') }}</p>
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||||
|
:aria-label="showPasswords ? t('extensions.rustdesk.hidePassword') : t('extensions.rustdesk.showPassword')"
|
||||||
|
@click="showPasswords = !showPasswords"
|
||||||
|
>
|
||||||
|
<Eye v-if="!showPasswords" class="h-4 w-4" />
|
||||||
|
<EyeOff v-else class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4259,7 +4268,7 @@ watch(isWindows, () => {
|
|||||||
</Card>
|
</Card>
|
||||||
<!-- Save button -->
|
<!-- Save button -->
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<Button :disabled="loading" @click="saveRustdeskConfig">
|
<Button :disabled="loading || rustdeskStatus?.service_status === 'running'" @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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user