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