feat: 引入统一配置 store 并迁移 Console/Settings 配置读写;自动显示当前使用的配置

This commit is contained in:
mofeng
2026-01-30 15:24:26 +08:00
parent 6a110258b9
commit d649c1ac20
7 changed files with 654 additions and 131 deletions

View File

@@ -20,6 +20,7 @@ import {
} from '@/components/ui/select' } from '@/components/ui/select'
import { Volume2, RefreshCw, Loader2 } from 'lucide-vue-next' import { Volume2, RefreshCw, Loader2 } from 'lucide-vue-next'
import { audioApi, configApi } from '@/api' import { audioApi, configApi } from '@/api'
import { useConfigStore } from '@/stores/config'
import { useSystemStore } from '@/stores/system' import { useSystemStore } from '@/stores/system'
import { getUnifiedAudio } from '@/composables/useUnifiedAudio' import { getUnifiedAudio } from '@/composables/useUnifiedAudio'
@@ -37,6 +38,7 @@ const emit = defineEmits<{
}>() }>()
const { t } = useI18n() const { t } = useI18n()
const configStore = useConfigStore()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const unifiedAudio = getUnifiedAudio() const unifiedAudio = getUnifiedAudio()
@@ -87,9 +89,9 @@ async function loadDevices() {
// Initialize from current config // Initialize from current config
function initializeFromCurrent() { function initializeFromCurrent() {
const audio = systemStore.audio const audio = configStore.audio
if (audio) { if (audio) {
audioEnabled.value = audio.available && audio.streaming audioEnabled.value = audio.enabled
selectedDevice.value = audio.device || '' selectedDevice.value = audio.device || ''
selectedQuality.value = (audio.quality as 'voice' | 'balanced' | 'high') || 'balanced' selectedQuality.value = (audio.quality as 'voice' | 'balanced' | 'high') || 'balanced'
} }
@@ -104,12 +106,10 @@ async function applyConfig() {
try { try {
// Update config // Update config
await configApi.update({ await configStore.updateAudio({
audio: { enabled: audioEnabled.value,
enabled: audioEnabled.value, device: selectedDevice.value,
device: selectedDevice.value, quality: selectedQuality.value,
quality: selectedQuality.value,
},
}) })
// If enabled and device is selected, try to start audio stream // If enabled and device is selected, try to start audio stream
@@ -151,12 +151,19 @@ async function applyConfig() {
// Watch popover open state // Watch popover open state
watch(() => props.open, (isOpen) => { watch(() => props.open, (isOpen) => {
if (isOpen) { if (!isOpen) return
if (devices.value.length === 0) {
loadDevices() if (devices.value.length === 0) {
} loadDevices()
initializeFromCurrent()
} }
configStore.refreshAudio()
.then(() => {
initializeFromCurrent()
})
.catch(() => {
initializeFromCurrent()
})
}) })
</script> </script>

View File

@@ -22,7 +22,9 @@ import {
import { MousePointer, Move, Loader2, RefreshCw } from 'lucide-vue-next' import { MousePointer, Move, Loader2, RefreshCw } from 'lucide-vue-next'
import HelpTooltip from '@/components/HelpTooltip.vue' import HelpTooltip from '@/components/HelpTooltip.vue'
import { configApi } from '@/api' import { configApi } from '@/api'
import { useSystemStore } from '@/stores/system' import { useConfigStore } from '@/stores/config'
import { HidBackend } from '@/types/generated'
import type { HidConfigUpdate } from '@/types/generated'
const props = defineProps<{ const props = defineProps<{
open: boolean open: boolean
@@ -35,7 +37,7 @@ const emit = defineEmits<{
}>() }>()
const { t } = useI18n() const { t } = useI18n()
const systemStore = useSystemStore() const configStore = useConfigStore()
const DEFAULT_MOUSE_MOVE_SEND_INTERVAL_MS = 16 const DEFAULT_MOUSE_MOVE_SEND_INTERVAL_MS = 16
@@ -72,7 +74,7 @@ watch(showCursor, (newValue, oldValue) => {
}) })
// HID Device Settings (requires apply) // HID Device Settings (requires apply)
const hidBackend = ref<'otg' | 'ch9329' | 'none'>('none') const hidBackend = ref<HidBackend>(HidBackend.None)
const devicePath = ref<string>('') const devicePath = ref<string>('')
const baudrate = ref<number>(9600) const baudrate = ref<number>(9600)
@@ -89,9 +91,9 @@ const buttonText = computed(() => t('actionbar.hidConfig'))
// Available device paths based on backend type // Available device paths based on backend type
const availableDevicePaths = computed(() => { const availableDevicePaths = computed(() => {
if (hidBackend.value === 'ch9329') { if (hidBackend.value === HidBackend.Ch9329) {
return serialDevices.value return serialDevices.value
} else if (hidBackend.value === 'otg') { } else if (hidBackend.value === HidBackend.Otg) {
// For OTG, we show UDC devices // For OTG, we show UDC devices
return udcDevices.value.map(udc => ({ return udcDevices.value.map(udc => ({
path: udc.name, path: udc.name,
@@ -124,9 +126,17 @@ function initializeFromCurrent() {
showCursor.value = storedCursor showCursor.value = storedCursor
// Initialize HID device settings from system state // Initialize HID device settings from system state
const hid = systemStore.hid const hid = configStore.hid
if (hid) { if (hid) {
hidBackend.value = (hid.backend as 'otg' | 'ch9329' | 'none') || 'none' hidBackend.value = hid.backend || HidBackend.None
if (hidBackend.value === HidBackend.Ch9329) {
devicePath.value = hid.ch9329_port || ''
baudrate.value = hid.ch9329_baudrate || 9600
} else if (hidBackend.value === HidBackend.Otg) {
devicePath.value = hid.otg_udc || ''
} else {
devicePath.value = ''
}
} }
} }
@@ -136,10 +146,8 @@ function toggleMouseMode() {
emit('update:mouseMode', newMode) emit('update:mouseMode', newMode)
// Update backend config // Update backend config
configApi.update({ configStore.updateHid({
hid: { mouse_absolute: newMode === 'absolute',
mouse_absolute: newMode === 'absolute',
},
}).catch(_e => { }).catch(_e => {
console.info('[HidConfig] Failed to update mouse mode') console.info('[HidConfig] Failed to update mouse mode')
toast.error(t('config.updateFailed')) toast.error(t('config.updateFailed'))
@@ -162,7 +170,11 @@ function handleThrottleChange(value: number[] | undefined) {
// Handle backend change // Handle backend change
function handleBackendChange(backend: unknown) { function handleBackendChange(backend: unknown) {
if (typeof backend !== 'string') return if (typeof backend !== 'string') return
hidBackend.value = backend as 'otg' | 'ch9329' | 'none' if (backend === HidBackend.Otg || backend === HidBackend.Ch9329 || backend === HidBackend.None) {
hidBackend.value = backend
} else {
return
}
// Clear device path when changing backend // Clear device path when changing backend
devicePath.value = '' devicePath.value = ''
@@ -189,18 +201,18 @@ function handleBaudrateChange(rate: unknown) {
async function applyHidConfig() { async function applyHidConfig() {
applying.value = true applying.value = true
try { try {
const config: Record<string, unknown> = { const config: HidConfigUpdate = {
backend: hidBackend.value, backend: hidBackend.value,
} }
if (hidBackend.value === 'ch9329') { if (hidBackend.value === HidBackend.Ch9329) {
config.ch9329_port = devicePath.value config.ch9329_port = devicePath.value
config.ch9329_baudrate = baudrate.value config.ch9329_baudrate = baudrate.value
} else if (hidBackend.value === 'otg') { } else if (hidBackend.value === HidBackend.Otg) {
config.otg_udc = devicePath.value config.otg_udc = devicePath.value
} }
await configApi.update({ hid: config }) await configStore.updateHid(config)
toast.success(t('config.applied')) toast.success(t('config.applied'))
@@ -215,14 +227,20 @@ async function applyHidConfig() {
// Watch open state // Watch open state
watch(() => props.open, (isOpen) => { watch(() => props.open, (isOpen) => {
if (isOpen) { if (!isOpen) return
// Load devices on first open
if (serialDevices.value.length === 0) { // Load devices on first open
loadDevices() if (serialDevices.value.length === 0) {
} loadDevices()
// Initialize from current config
initializeFromCurrent()
} }
configStore.refreshHid()
.then(() => {
initializeFromCurrent()
})
.catch(() => {
initializeFromCurrent()
})
}) })
</script> </script>
@@ -331,15 +349,15 @@ watch(() => props.open, (isOpen) => {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="otg" class="text-xs">USB OTG</SelectItem> <SelectItem :value="HidBackend.Otg" class="text-xs">USB OTG</SelectItem>
<SelectItem value="ch9329" class="text-xs">CH9329 (Serial)</SelectItem> <SelectItem :value="HidBackend.Ch9329" class="text-xs">CH9329 (Serial)</SelectItem>
<SelectItem value="none" class="text-xs">{{ t('common.disabled') }}</SelectItem> <SelectItem :value="HidBackend.None" class="text-xs">{{ t('common.disabled') }}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<!-- Device Path (OTG or CH9329) --> <!-- Device Path (OTG or CH9329) -->
<div v-if="hidBackend !== 'none'" class="space-y-2"> <div v-if="hidBackend !== HidBackend.None" class="space-y-2">
<Label class="text-xs text-muted-foreground">{{ t('actionbar.devicePath') }}</Label> <Label class="text-xs text-muted-foreground">{{ t('actionbar.devicePath') }}</Label>
<Select <Select
:model-value="devicePath" :model-value="devicePath"
@@ -363,7 +381,7 @@ watch(() => props.open, (isOpen) => {
</div> </div>
<!-- Baudrate (CH9329 only) --> <!-- Baudrate (CH9329 only) -->
<div v-if="hidBackend === 'ch9329'" class="space-y-2"> <div v-if="hidBackend === HidBackend.Ch9329" class="space-y-2">
<Label class="text-xs text-muted-foreground">{{ t('actionbar.baudrate') }}</Label> <Label class="text-xs text-muted-foreground">{{ t('actionbar.baudrate') }}</Label>
<Select <Select
:model-value="String(baudrate)" :model-value="String(baudrate)"

View File

@@ -20,7 +20,7 @@ import {
import { Monitor, RefreshCw, Loader2, Settings, Zap, Scale, Image } from 'lucide-vue-next' import { Monitor, RefreshCw, Loader2, Settings, Zap, Scale, Image } from 'lucide-vue-next'
import HelpTooltip from '@/components/HelpTooltip.vue' import HelpTooltip from '@/components/HelpTooltip.vue'
import { configApi, streamApi, type VideoCodecInfo, type EncoderBackendInfo, type BitratePreset } from '@/api' import { configApi, streamApi, type VideoCodecInfo, type EncoderBackendInfo, type BitratePreset } from '@/api'
import { useSystemStore } from '@/stores/system' import { useConfigStore } from '@/stores/config'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
export type VideoMode = 'mjpeg' | 'h264' | 'h265' | 'vp8' | 'vp9' export type VideoMode = 'mjpeg' | 'h264' | 'h265' | 'vp8' | 'vp9'
@@ -51,7 +51,7 @@ const emit = defineEmits<{
}>() }>()
const { t } = useI18n() const { t } = useI18n()
const systemStore = useSystemStore() const configStore = useConfigStore()
const router = useRouter() const router = useRouter()
// Device list // Device list
@@ -64,7 +64,7 @@ const loadingCodecs = ref(false)
// Backend list // Backend list
const backends = ref<EncoderBackendInfo[]>([]) const backends = ref<EncoderBackendInfo[]>([])
const currentEncoderBackend = ref<string>('auto') const currentEncoderBackend = computed(() => configStore.stream?.encoder || 'auto')
// Browser supported codecs (WebRTC receive capabilities) // Browser supported codecs (WebRTC receive capabilities)
const browserSupportedCodecs = ref<Set<string>>(new Set()) const browserSupportedCodecs = ref<Set<string>>(new Set())
@@ -197,11 +197,11 @@ const applyingBitrate = ref(false)
// Current config from store // Current config from store
const currentConfig = computed(() => ({ const currentConfig = computed(() => ({
device: systemStore.stream?.device || '', device: configStore.video?.device || '',
format: systemStore.stream?.format || '', format: configStore.video?.format || '',
width: systemStore.stream?.resolution?.[0] || 1920, width: configStore.video?.width || 1920,
height: systemStore.stream?.resolution?.[1] || 1080, height: configStore.video?.height || 1080,
fps: systemStore.stream?.targetFps || 30, fps: configStore.video?.fps || 30,
})) }))
// Button display text - simplified to just show label // Button display text - simplified to just show label
@@ -303,19 +303,6 @@ async function loadCodecs() {
} }
} }
// Load current encoder backend from config
async function loadEncoderBackend() {
try {
const config = await configApi.get()
// Access nested stream.encoder
const streamConfig = config.stream as { encoder?: string } | undefined
currentEncoderBackend.value = streamConfig?.encoder || 'auto'
} catch (e) {
console.info('[VideoConfig] Failed to load encoder backend config')
currentEncoderBackend.value = 'auto'
}
}
// Navigate to settings page (video tab) // Navigate to settings page (video tab)
function goToSettings() { function goToSettings() {
router.push('/settings?tab=video') router.push('/settings?tab=video')
@@ -440,14 +427,12 @@ async function applyVideoConfig() {
applying.value = true applying.value = true
try { try {
await configApi.update({ await configStore.updateVideo({
video: { device: selectedDevice.value,
device: selectedDevice.value, format: selectedFormat.value,
format: selectedFormat.value, width,
width, height,
height, fps: selectedFps.value,
fps: selectedFps.value,
},
}) })
toast.success(t('config.applied')) toast.success(t('config.applied'))
@@ -463,26 +448,32 @@ async function applyVideoConfig() {
// Watch open state // Watch open state
watch(() => props.open, (isOpen) => { watch(() => props.open, (isOpen) => {
if (isOpen) { if (!isOpen) {
// Detect browser codec support on first open
if (browserSupportedCodecs.value.size === 0) {
detectBrowserCodecSupport()
}
// Load devices on first open
if (devices.value.length === 0) {
loadDevices()
}
// Load codecs and backends on first open
if (codecs.value.length === 0) {
loadCodecs()
}
// Load encoder backend config
loadEncoderBackend()
// Initialize from current config
initializeFromCurrent()
} else {
isDirty.value = false isDirty.value = false
return
} }
// Detect browser codec support on first open
if (browserSupportedCodecs.value.size === 0) {
detectBrowserCodecSupport()
}
// Load devices on first open
if (devices.value.length === 0) {
loadDevices()
}
// Load codecs and backends on first open
if (codecs.value.length === 0) {
loadCodecs()
}
Promise.all([
configStore.refreshVideo(),
configStore.refreshStream(),
]).then(() => {
initializeFromCurrent()
}).catch(() => {
initializeFromCurrent()
})
}) })
// Sync selected values when backend config changes (e.g., auto format switch on mode change) // Sync selected values when backend config changes (e.g., auto format switch on mode change)

506
web/src/stores/config.ts Normal file
View File

@@ -0,0 +1,506 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import {
authConfigApi,
atxConfigApi,
audioConfigApi,
hidConfigApi,
msdConfigApi,
rustdeskConfigApi,
streamConfigApi,
videoConfigApi,
webConfigApi,
} from '@/api'
import type {
AtxConfig,
AtxConfigUpdate,
AudioConfig,
AudioConfigUpdate,
AuthConfig,
AuthConfigUpdate,
HidConfig,
HidConfigUpdate,
MsdConfig,
MsdConfigUpdate,
StreamConfigResponse,
StreamConfigUpdate,
VideoConfig,
VideoConfigUpdate,
WebConfig,
WebConfigUpdate,
} from '@/types/generated'
import type {
RustDeskConfigResponse as ApiRustDeskConfigResponse,
RustDeskConfigUpdate as ApiRustDeskConfigUpdate,
RustDeskStatusResponse as ApiRustDeskStatusResponse,
RustDeskPasswordResponse as ApiRustDeskPasswordResponse,
} from '@/api'
function normalizeErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message
if (typeof error === 'string') return error
return 'Unknown error'
}
export const useConfigStore = defineStore('config', () => {
const auth = ref<AuthConfig | null>(null)
const video = ref<VideoConfig | null>(null)
const audio = ref<AudioConfig | null>(null)
const hid = ref<HidConfig | null>(null)
const msd = ref<MsdConfig | null>(null)
const stream = ref<StreamConfigResponse | null>(null)
const web = ref<WebConfig | null>(null)
const atx = ref<AtxConfig | null>(null)
const rustdeskConfig = ref<ApiRustDeskConfigResponse | null>(null)
const rustdeskStatus = ref<ApiRustDeskStatusResponse | null>(null)
const rustdeskPassword = ref<ApiRustDeskPasswordResponse | null>(null)
const authLoading = ref(false)
const videoLoading = ref(false)
const audioLoading = ref(false)
const hidLoading = ref(false)
const msdLoading = ref(false)
const streamLoading = ref(false)
const webLoading = ref(false)
const atxLoading = ref(false)
const rustdeskLoading = ref(false)
const authError = ref<string | null>(null)
const videoError = ref<string | null>(null)
const audioError = ref<string | null>(null)
const hidError = ref<string | null>(null)
const msdError = ref<string | null>(null)
const streamError = ref<string | null>(null)
const webError = ref<string | null>(null)
const atxError = ref<string | null>(null)
const rustdeskError = ref<string | null>(null)
let authPromise: Promise<AuthConfig> | null = null
let videoPromise: Promise<VideoConfig> | null = null
let audioPromise: Promise<AudioConfig> | null = null
let hidPromise: Promise<HidConfig> | null = null
let msdPromise: Promise<MsdConfig> | null = null
let streamPromise: Promise<StreamConfigResponse> | null = null
let webPromise: Promise<WebConfig> | null = null
let atxPromise: Promise<AtxConfig> | null = null
let rustdeskPromise: Promise<ApiRustDeskConfigResponse> | null = null
let rustdeskStatusPromise: Promise<ApiRustDeskStatusResponse> | null = null
let rustdeskPasswordPromise: Promise<ApiRustDeskPasswordResponse> | null = null
async function refreshAuth() {
if (authLoading.value && authPromise) return authPromise
authLoading.value = true
authError.value = null
const request = authConfigApi.get()
.then((response) => {
auth.value = response
return response
})
.catch((error) => {
authError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
authLoading.value = false
authPromise = null
})
authPromise = request
return request
}
async function refreshVideo() {
if (videoLoading.value && videoPromise) return videoPromise
videoLoading.value = true
videoError.value = null
const request = videoConfigApi.get()
.then((response) => {
video.value = response
return response
})
.catch((error) => {
videoError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
videoLoading.value = false
videoPromise = null
})
videoPromise = request
return request
}
async function refreshAudio() {
if (audioLoading.value && audioPromise) return audioPromise
audioLoading.value = true
audioError.value = null
const request = audioConfigApi.get()
.then((response) => {
audio.value = response
return response
})
.catch((error) => {
audioError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
audioLoading.value = false
audioPromise = null
})
audioPromise = request
return request
}
async function refreshHid() {
if (hidLoading.value && hidPromise) return hidPromise
hidLoading.value = true
hidError.value = null
const request = hidConfigApi.get()
.then((response) => {
hid.value = response
return response
})
.catch((error) => {
hidError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
hidLoading.value = false
hidPromise = null
})
hidPromise = request
return request
}
async function refreshMsd() {
if (msdLoading.value && msdPromise) return msdPromise
msdLoading.value = true
msdError.value = null
const request = msdConfigApi.get()
.then((response) => {
msd.value = response
return response
})
.catch((error) => {
msdError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
msdLoading.value = false
msdPromise = null
})
msdPromise = request
return request
}
async function refreshStream() {
if (streamLoading.value && streamPromise) return streamPromise
streamLoading.value = true
streamError.value = null
const request = streamConfigApi.get()
.then((response) => {
stream.value = response
return response
})
.catch((error) => {
streamError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
streamLoading.value = false
streamPromise = null
})
streamPromise = request
return request
}
async function refreshWeb() {
if (webLoading.value && webPromise) return webPromise
webLoading.value = true
webError.value = null
const request = webConfigApi.get()
.then((response) => {
web.value = response
return response
})
.catch((error) => {
webError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
webLoading.value = false
webPromise = null
})
webPromise = request
return request
}
async function refreshAtx() {
if (atxLoading.value && atxPromise) return atxPromise
atxLoading.value = true
atxError.value = null
const request = atxConfigApi.get()
.then((response) => {
atx.value = response
return response
})
.catch((error) => {
atxError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
atxLoading.value = false
atxPromise = null
})
atxPromise = request
return request
}
async function refreshRustdeskConfig() {
if (rustdeskLoading.value && rustdeskPromise) return rustdeskPromise
rustdeskLoading.value = true
rustdeskError.value = null
const request = rustdeskConfigApi.get()
.then((response) => {
rustdeskConfig.value = response
return response
})
.catch((error) => {
rustdeskError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
rustdeskLoading.value = false
rustdeskPromise = null
})
rustdeskPromise = request
return request
}
async function refreshRustdeskStatus() {
if (rustdeskLoading.value && rustdeskStatusPromise) return rustdeskStatusPromise
rustdeskLoading.value = true
rustdeskError.value = null
const request = rustdeskConfigApi.getStatus()
.then((response) => {
rustdeskStatus.value = response
rustdeskConfig.value = response.config
return response
})
.catch((error) => {
rustdeskError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
rustdeskLoading.value = false
rustdeskStatusPromise = null
})
rustdeskStatusPromise = request
return request
}
async function refreshRustdeskPassword() {
if (rustdeskLoading.value && rustdeskPasswordPromise) return rustdeskPasswordPromise
rustdeskLoading.value = true
rustdeskError.value = null
const request = rustdeskConfigApi.getPassword()
.then((response) => {
rustdeskPassword.value = response
return response
})
.catch((error) => {
rustdeskError.value = normalizeErrorMessage(error)
throw error
})
.finally(() => {
rustdeskLoading.value = false
rustdeskPasswordPromise = null
})
rustdeskPasswordPromise = request
return request
}
function ensureAuth() {
if (auth.value) return Promise.resolve(auth.value)
return refreshAuth()
}
function ensureVideo() {
if (video.value) return Promise.resolve(video.value)
return refreshVideo()
}
function ensureAudio() {
if (audio.value) return Promise.resolve(audio.value)
return refreshAudio()
}
function ensureHid() {
if (hid.value) return Promise.resolve(hid.value)
return refreshHid()
}
function ensureMsd() {
if (msd.value) return Promise.resolve(msd.value)
return refreshMsd()
}
function ensureStream() {
if (stream.value) return Promise.resolve(stream.value)
return refreshStream()
}
function ensureWeb() {
if (web.value) return Promise.resolve(web.value)
return refreshWeb()
}
function ensureAtx() {
if (atx.value) return Promise.resolve(atx.value)
return refreshAtx()
}
function ensureRustdeskConfig() {
if (rustdeskConfig.value) return Promise.resolve(rustdeskConfig.value)
return refreshRustdeskConfig()
}
async function updateAuth(update: AuthConfigUpdate) {
const response = await authConfigApi.update(update)
auth.value = response
return response
}
async function updateVideo(update: VideoConfigUpdate) {
const response = await videoConfigApi.update(update)
video.value = response
return response
}
async function updateAudio(update: AudioConfigUpdate) {
const response = await audioConfigApi.update(update)
audio.value = response
return response
}
async function updateHid(update: HidConfigUpdate) {
const response = await hidConfigApi.update(update)
hid.value = response
return response
}
async function updateMsd(update: MsdConfigUpdate) {
const response = await msdConfigApi.update(update)
msd.value = response
return response
}
async function updateStream(update: StreamConfigUpdate) {
const response = await streamConfigApi.update(update)
stream.value = response
return response
}
async function updateWeb(update: WebConfigUpdate) {
const response = await webConfigApi.update(update)
web.value = response
return response
}
async function updateAtx(update: AtxConfigUpdate) {
const response = await atxConfigApi.update(update)
atx.value = response
return response
}
async function updateRustdesk(update: ApiRustDeskConfigUpdate) {
const response = await rustdeskConfigApi.update(update)
rustdeskConfig.value = response
return response
}
async function regenerateRustdeskId() {
const response = await rustdeskConfigApi.regenerateId()
rustdeskConfig.value = response
return response
}
async function regenerateRustdeskPassword() {
const response = await rustdeskConfigApi.regeneratePassword()
rustdeskConfig.value = response
return response
}
return {
auth,
video,
audio,
hid,
msd,
stream,
web,
atx,
rustdeskConfig,
rustdeskStatus,
rustdeskPassword,
authLoading,
videoLoading,
audioLoading,
hidLoading,
msdLoading,
streamLoading,
webLoading,
atxLoading,
rustdeskLoading,
authError,
videoError,
audioError,
hidError,
msdError,
streamError,
webError,
atxError,
rustdeskError,
refreshAuth,
refreshVideo,
refreshAudio,
refreshHid,
refreshMsd,
refreshStream,
refreshWeb,
refreshAtx,
refreshRustdeskConfig,
refreshRustdeskStatus,
refreshRustdeskPassword,
ensureAuth,
ensureVideo,
ensureAudio,
ensureHid,
ensureMsd,
ensureStream,
ensureWeb,
ensureAtx,
ensureRustdeskConfig,
updateAuth,
updateVideo,
updateAudio,
updateHid,
updateMsd,
updateStream,
updateWeb,
updateAtx,
updateRustdesk,
regenerateRustdeskId,
regenerateRustdeskPassword,
}
})

View File

@@ -1,2 +1,3 @@
export { useAuthStore } from './auth' export { useAuthStore } from './auth'
export { useConfigStore } from './config'
export { useSystemStore } from './system' export { useSystemStore } from './system'

View File

@@ -3,6 +3,7 @@ import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useSystemStore } from '@/stores/system' import { useSystemStore } from '@/stores/system'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useWebSocket } from '@/composables/useWebSocket' import { useWebSocket } from '@/composables/useWebSocket'
import { useConsoleEvents } from '@/composables/useConsoleEvents' import { useConsoleEvents } from '@/composables/useConsoleEvents'
@@ -59,6 +60,7 @@ import { setLanguage } from '@/i18n'
const { t, locale } = useI18n() const { t, locale } = useI18n()
const router = useRouter() const router = useRouter()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const configStore = useConfigStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const { connected: wsConnected, networkError: wsNetworkError } = useWebSocket() const { connected: wsConnected, networkError: wsNetworkError } = useWebSocket()
const hidWs = useHidWebSocket() const hidWs = useHidWebSocket()
@@ -134,6 +136,15 @@ let accumulatedDelta = { x: 0, y: 0 } // For relative mode: accumulate deltas be
// Cursor visibility (from localStorage, updated via storage event) // Cursor visibility (from localStorage, updated via storage event)
const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false') const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false')
function syncMouseModeFromConfig() {
const mouseAbsolute = configStore.hid?.mouse_absolute
if (typeof mouseAbsolute !== 'boolean') return
const nextMode: 'absolute' | 'relative' = mouseAbsolute ? 'absolute' : 'relative'
if (mouseMode.value !== nextMode) {
mouseMode.value = nextMode
}
}
// Virtual keyboard state // Virtual keyboard state
const virtualKeyboardVisible = ref(false) const virtualKeyboardVisible = ref(false)
const virtualKeyboardAttached = ref(true) const virtualKeyboardAttached = ref(true)
@@ -1787,6 +1798,9 @@ onMounted(async () => {
// 4. 其他初始化 // 4. 其他初始化
await systemStore.startStream().catch(() => {}) await systemStore.startStream().catch(() => {})
await systemStore.fetchAllStates() await systemStore.fetchAllStates()
await configStore.refreshHid().then(() => {
syncMouseModeFromConfig()
}).catch(() => {})
window.addEventListener('keydown', handleKeyDown) window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp) window.addEventListener('keyup', handleKeyUp)
@@ -1800,6 +1814,10 @@ onMounted(async () => {
window.addEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener) window.addEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
window.addEventListener('storage', handleMouseSendIntervalStorage) window.addEventListener('storage', handleMouseSendIntervalStorage)
watch(() => configStore.hid?.mouse_absolute, () => {
syncMouseModeFromConfig()
})
// Pointer Lock event listeners // Pointer Lock event listeners
document.addEventListener('pointerlockchange', handlePointerLockChange) document.addEventListener('pointerlockchange', handlePointerLockChange)
document.addEventListener('pointerlockerror', handlePointerLockError) document.addEventListener('pointerlockerror', handlePointerLockError)

View File

@@ -2,20 +2,14 @@
import { ref, computed, onMounted, watch } from 'vue' import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useSystemStore } from '@/stores/system' import { useSystemStore } from '@/stores/system'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { import {
authApi, authApi,
authConfigApi,
configApi, configApi,
streamApi, streamApi,
videoConfigApi,
streamConfigApi,
hidConfigApi,
msdConfigApi,
atxConfigApi, atxConfigApi,
extensionsApi, extensionsApi,
rustdeskConfigApi,
webConfigApi,
systemApi, systemApi,
type EncoderBackendInfo, type EncoderBackendInfo,
type AuthConfig, type AuthConfig,
@@ -80,6 +74,7 @@ import {
const { t, locale } = useI18n() const { t, locale } = useI18n()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const configStore = useConfigStore()
const authStore = useAuthStore() const authStore = useAuthStore()
// Settings state // Settings state
@@ -271,7 +266,6 @@ const config = ref({
} as OtgHidFunctions, } as OtgHidFunctions,
msd_enabled: false, msd_enabled: false,
msd_dir: '', msd_dir: '',
network_port: 8080,
encoder_backend: 'auto', encoder_backend: 'auto',
// STUN/TURN settings // STUN/TURN settings
stun_server: '', stun_server: '',
@@ -583,7 +577,7 @@ async function saveConfig() {
// Video 配置(包括编码器和 WebRTC/STUN/TURN 设置) // Video 配置(包括编码器和 WebRTC/STUN/TURN 设置)
if (activeSection.value === 'video') { if (activeSection.value === 'video') {
savePromises.push( savePromises.push(
videoConfigApi.update({ configStore.updateVideo({
device: config.value.video_device || undefined, device: config.value.video_device || undefined,
format: config.value.video_format || undefined, format: config.value.video_format || undefined,
width: config.value.video_width, width: config.value.video_width,
@@ -593,7 +587,7 @@ async function saveConfig() {
) )
// 同时保存 Stream/Encoder 和 STUN/TURN 配置 // 同时保存 Stream/Encoder 和 STUN/TURN 配置
savePromises.push( savePromises.push(
streamConfigApi.update({ configStore.updateStream({
encoder: config.value.encoder_backend as any, encoder: config.value.encoder_backend as any,
stun_server: config.value.stun_server || undefined, stun_server: config.value.stun_server || undefined,
turn_server: config.value.turn_server || undefined, turn_server: config.value.turn_server || undefined,
@@ -642,12 +636,12 @@ async function saveConfig() {
hidUpdate.otg_profile = config.value.hid_otg_profile hidUpdate.otg_profile = config.value.hid_otg_profile
hidUpdate.otg_functions = { ...config.value.hid_otg_functions } hidUpdate.otg_functions = { ...config.value.hid_otg_functions }
} }
savePromises.push(hidConfigApi.update(hidUpdate)) savePromises.push(configStore.updateHid(hidUpdate))
if (config.value.msd_enabled !== desiredMsdEnabled) { if (config.value.msd_enabled !== desiredMsdEnabled) {
config.value.msd_enabled = desiredMsdEnabled config.value.msd_enabled = desiredMsdEnabled
} }
savePromises.push( savePromises.push(
msdConfigApi.update({ configStore.updateMsd({
enabled: desiredMsdEnabled, enabled: desiredMsdEnabled,
}) })
) )
@@ -656,7 +650,7 @@ async function saveConfig() {
// MSD 配置 // MSD 配置
if (activeSection.value === 'msd') { if (activeSection.value === 'msd') {
savePromises.push( savePromises.push(
msdConfigApi.update({ configStore.updateMsd({
msd_dir: config.value.msd_dir || undefined, msd_dir: config.value.msd_dir || undefined,
}) })
) )
@@ -677,10 +671,10 @@ async function loadConfig() {
try { try {
// 并行加载所有域配置 // 并行加载所有域配置
const [video, stream, hid, msd] = await Promise.all([ const [video, stream, hid, msd] = await Promise.all([
videoConfigApi.get(), configStore.refreshVideo(),
streamConfigApi.get(), configStore.refreshStream(),
hidConfigApi.get(), configStore.refreshHid(),
msdConfigApi.get(), configStore.refreshMsd(),
]) ])
config.value = { config.value = {
@@ -702,7 +696,6 @@ async function loadConfig() {
} as OtgHidFunctions, } as OtgHidFunctions,
msd_enabled: msd.enabled || false, msd_enabled: msd.enabled || false,
msd_dir: msd.msd_dir || '', msd_dir: msd.msd_dir || '',
network_port: 8080, // 从旧 API 加载
encoder_backend: stream.encoder || 'auto', encoder_backend: stream.encoder || 'auto',
// STUN/TURN settings // STUN/TURN settings
stun_server: stream.stun_server || '', stun_server: stream.stun_server || '',
@@ -723,14 +716,6 @@ async function loadConfig() {
otgSerialNumber.value = hid.otg_descriptor.serial_number || '' otgSerialNumber.value = hid.otg_descriptor.serial_number || ''
} }
// 加载 web config仍使用旧 API
try {
const fullConfig = await configApi.get()
const web = fullConfig.web as any || {}
config.value.network_port = web.http_port || 8080
} catch (e) {
console.warn('Failed to load web config:', e)
}
} catch (e) { } catch (e) {
console.error('Failed to load config:', e) console.error('Failed to load config:', e)
} finally { } finally {
@@ -763,7 +748,7 @@ async function loadBackends() {
async function loadAuthConfig() { async function loadAuthConfig() {
authConfigLoading.value = true authConfigLoading.value = true
try { try {
authConfig.value = await authConfigApi.get() authConfig.value = await configStore.refreshAuth()
} catch (e) { } catch (e) {
console.error('Failed to load auth config:', e) console.error('Failed to load auth config:', e)
} finally { } finally {
@@ -774,10 +759,9 @@ async function loadAuthConfig() {
async function saveAuthConfig() { async function saveAuthConfig() {
authConfigLoading.value = true authConfigLoading.value = true
try { try {
await authConfigApi.update({ authConfig.value = await configStore.updateAuth({
single_user_allow_multiple_sessions: authConfig.value.single_user_allow_multiple_sessions, single_user_allow_multiple_sessions: authConfig.value.single_user_allow_multiple_sessions,
}) })
await loadAuthConfig()
} catch (e) { } catch (e) {
console.error('Failed to save auth config:', e) console.error('Failed to save auth config:', e)
} finally { } finally {
@@ -912,7 +896,7 @@ function removeEasytierPeer(index: number) {
// ATX management functions // ATX management functions
async function loadAtxConfig() { async function loadAtxConfig() {
try { try {
const config = await atxConfigApi.get() const config = await configStore.refreshAtx()
atxConfig.value = { atxConfig.value = {
enabled: config.enabled, enabled: config.enabled,
power: { ...config.power }, power: { ...config.power },
@@ -937,7 +921,7 @@ async function saveAtxConfig() {
loading.value = true loading.value = true
saved.value = false saved.value = false
try { try {
await atxConfigApi.update({ await configStore.updateAtx({
enabled: atxConfig.value.enabled, enabled: atxConfig.value.enabled,
power: { power: {
driver: atxConfig.value.power.driver, driver: atxConfig.value.power.driver,
@@ -981,10 +965,8 @@ function getAtxDevicesForDriver(driver: string): string[] {
async function loadRustdeskConfig() { async function loadRustdeskConfig() {
rustdeskLoading.value = true rustdeskLoading.value = true
try { try {
const [config, status] = await Promise.all([ const status = await configStore.refreshRustdeskStatus()
rustdeskConfigApi.get(), const config = status.config
rustdeskConfigApi.getStatus(),
])
rustdeskConfig.value = config rustdeskConfig.value = config
rustdeskStatus.value = status rustdeskStatus.value = status
rustdeskLocalConfig.value = { rustdeskLocalConfig.value = {
@@ -1002,7 +984,7 @@ async function loadRustdeskConfig() {
async function loadRustdeskPassword() { async function loadRustdeskPassword() {
try { try {
rustdeskPassword.value = await rustdeskConfigApi.getPassword() rustdeskPassword.value = await configStore.refreshRustdeskPassword()
} catch (e) { } catch (e) {
console.error('Failed to load RustDesk password:', e) console.error('Failed to load RustDesk password:', e)
} }
@@ -1060,7 +1042,7 @@ function removeBindAddress(index: number) {
// Web server config functions // Web server config functions
async function loadWebServerConfig() { async function loadWebServerConfig() {
try { try {
const config = await webConfigApi.get() const config = await configStore.refreshWeb()
webServerConfig.value = config webServerConfig.value = config
applyBindStateFromConfig(config) applyBindStateFromConfig(config)
} catch (e) { } catch (e) {
@@ -1078,7 +1060,7 @@ async function saveWebServerConfig() {
https_enabled: webServerConfig.value.https_enabled, https_enabled: webServerConfig.value.https_enabled,
bind_addresses: effectiveBindAddresses.value, bind_addresses: effectiveBindAddresses.value,
} }
const updated = await webConfigApi.update(update) const updated = await configStore.updateWeb(update)
webServerConfig.value = updated webServerConfig.value = updated
applyBindStateFromConfig(updated) applyBindStateFromConfig(updated)
showRestartDialog.value = true showRestartDialog.value = true
@@ -1117,7 +1099,7 @@ async function saveRustdeskConfig() {
21116, 21116,
) )
const relayServer = normalizeRustdeskServer(rustdeskLocalConfig.value.relay_server, 21117) const relayServer = normalizeRustdeskServer(rustdeskLocalConfig.value.relay_server, 21117)
await rustdeskConfigApi.update({ await configStore.updateRustdesk({
enabled: rustdeskLocalConfig.value.enabled, enabled: rustdeskLocalConfig.value.enabled,
rendezvous_server: rendezvousServer, rendezvous_server: rendezvousServer,
relay_server: relayServer, relay_server: relayServer,
@@ -1139,7 +1121,7 @@ async function regenerateRustdeskId() {
if (!confirm(t('extensions.rustdesk.confirmRegenerateId'))) return if (!confirm(t('extensions.rustdesk.confirmRegenerateId'))) return
rustdeskLoading.value = true rustdeskLoading.value = true
try { try {
await rustdeskConfigApi.regenerateId() await configStore.regenerateRustdeskId()
await loadRustdeskConfig() await loadRustdeskConfig()
await loadRustdeskPassword() await loadRustdeskPassword()
} catch (e) { } catch (e) {
@@ -1153,7 +1135,7 @@ async function regenerateRustdeskPassword() {
if (!confirm(t('extensions.rustdesk.confirmRegeneratePassword'))) return if (!confirm(t('extensions.rustdesk.confirmRegeneratePassword'))) return
rustdeskLoading.value = true rustdeskLoading.value = true
try { try {
await rustdeskConfigApi.regeneratePassword() await configStore.regenerateRustdeskPassword()
await loadRustdeskConfig() await loadRustdeskConfig()
await loadRustdeskPassword() await loadRustdeskPassword()
} catch (e) { } catch (e) {
@@ -1167,7 +1149,7 @@ async function startRustdesk() {
rustdeskLoading.value = true rustdeskLoading.value = true
try { try {
// Enable and save config to start the service // Enable and save config to start the service
await rustdeskConfigApi.update({ enabled: true }) await configStore.updateRustdesk({ enabled: true })
rustdeskLocalConfig.value.enabled = true rustdeskLocalConfig.value.enabled = true
await loadRustdeskConfig() await loadRustdeskConfig()
} catch (e) { } catch (e) {
@@ -1181,7 +1163,7 @@ async function stopRustdesk() {
rustdeskLoading.value = true rustdeskLoading.value = true
try { try {
// Disable and save config to stop the service // Disable and save config to stop the service
await rustdeskConfigApi.update({ enabled: false }) await configStore.updateRustdesk({ enabled: false })
rustdeskLocalConfig.value.enabled = false rustdeskLocalConfig.value.enabled = false
await loadRustdeskConfig() await loadRustdeskConfig()
} catch (e) { } catch (e) {