mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-28 08:20:49 +08:00
feat(platform): 按平台能力控制功能入口与请求
This commit is contained in:
@@ -10,7 +10,7 @@ pub fn capabilities() -> PlatformCapabilities {
|
|||||||
encoder: FeatureCapability::available([
|
encoder: FeatureCapability::available([
|
||||||
"software", "vaapi", "nvenc", "qsv", "amf", "rkmpp", "v4l2m2m",
|
"software", "vaapi", "nvenc", "qsv", "amf", "rkmpp", "v4l2m2m",
|
||||||
]),
|
]),
|
||||||
hid: FeatureCapability::available(["otg", "ch9329", "none"]),
|
hid: FeatureCapability::available(["otg", "ch9329", "bluetooth", "none"]),
|
||||||
atx: FeatureCapability::available(["gpio", "usb_relay", "serial", "wol", "none"]),
|
atx: FeatureCapability::available(["gpio", "usb_relay", "serial", "wol", "none"]),
|
||||||
msd: FeatureCapability::available(["configfs"]),
|
msd: FeatureCapability::available(["configfs"]),
|
||||||
otg: FeatureCapability::available(["configfs"]),
|
otg: FeatureCapability::available(["configfs"]),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
import { useSystemStore } from '@/stores/system'
|
||||||
import { request } from '@/api/request'
|
import { request } from '@/api/request'
|
||||||
import type { HidConfig, MsdConfig } from '@/types/generated'
|
import type { HidConfig, MsdConfig } from '@/types/generated'
|
||||||
import { useHidConnection } from '@/composables/useHidConnection'
|
import { useHidConnection } from '@/composables/useHidConnection'
|
||||||
@@ -14,6 +15,9 @@ import HidDeviceOverview from './HidDeviceOverview.vue'
|
|||||||
const props = defineProps<{ dirty?: boolean }>()
|
const props = defineProps<{ dirty?: boolean }>()
|
||||||
const emit = defineEmits<{ close: []; applied: []; discard: [] }>()
|
const emit = defineEmits<{ close: []; applied: []; discard: [] }>()
|
||||||
const { t } = useI18n(), store = useConfigStore()
|
const { t } = useI18n(), store = useConfigStore()
|
||||||
|
const systemStore = useSystemStore()
|
||||||
|
const otgSupported = computed(() => systemStore.platform?.otg.available === true)
|
||||||
|
const msdSupported = computed(() => systemStore.platform?.msd.available === true)
|
||||||
const draft = ref<HidSelection>(selectionFrom(store.hid))
|
const draft = ref<HidSelection>(selectionFrom(store.hid))
|
||||||
const valid = ref(false), busy = ref(false), error = ref(''), applied = ref(false), loaded = ref(false)
|
const valid = ref(false), busy = ref(false), error = ref(''), applied = ref(false), loaded = ref(false)
|
||||||
const acknowledged = ref(!props.dirty), uncertain = ref(false)
|
const acknowledged = ref(!props.dirty), uncertain = ref(false)
|
||||||
@@ -52,7 +56,12 @@ async function refreshMsd() {
|
|||||||
return msd
|
return msd
|
||||||
}
|
}
|
||||||
async function refreshConfigs() {
|
async function refreshConfigs() {
|
||||||
const results = await Promise.allSettled([refreshHid(), refreshMsd(), readConfig<{ enabled: boolean }>('/config/otg-network'), readConfig<{ enabled: boolean }>('/config/uac')])
|
const requests: Promise<unknown>[] = [refreshHid()]
|
||||||
|
if (msdSupported.value) requests.push(refreshMsd())
|
||||||
|
if (otgSupported.value) {
|
||||||
|
requests.push(readConfig<{ enabled: boolean }>('/config/otg-network'), readConfig<{ enabled: boolean }>('/config/uac'))
|
||||||
|
}
|
||||||
|
const results = await Promise.allSettled(requests)
|
||||||
emit('applied')
|
emit('applied')
|
||||||
const failure = results.find(r => r.status === 'rejected')
|
const failure = results.find(r => r.status === 'rejected')
|
||||||
if (failure?.status === 'rejected') throw failure.reason
|
if (failure?.status === 'rejected') throw failure.reason
|
||||||
@@ -115,9 +124,10 @@ async function close() {
|
|||||||
async function load() {
|
async function load() {
|
||||||
loaded.value = false; error.value = ''
|
loaded.value = false; error.value = ''
|
||||||
try {
|
try {
|
||||||
|
if (!systemStore.platform) await systemStore.fetchSystemInfo()
|
||||||
await refreshHid()
|
await refreshHid()
|
||||||
draft.value = selectionFrom(store.hid)
|
draft.value = selectionFrom(store.hid)
|
||||||
if (store.hid?.backend === 'otg') {
|
if (otgSupported.value && store.hid?.backend === 'otg') {
|
||||||
const [msd, network, audio] = await Promise.all([refreshMsd(), readConfig<{ enabled: boolean }>('/config/otg-network'), readConfig<{ enabled: boolean }>('/config/uac')])
|
const [msd, network, audio] = await Promise.all([refreshMsd(), readConfig<{ enabled: boolean }>('/config/otg-network'), readConfig<{ enabled: boolean }>('/config/uac')])
|
||||||
disabledUsb.value = [msd.enabled ? t('hidGuide.msd') : '', network.enabled ? t('hidGuide.network') : '', audio.enabled ? t('hidGuide.audio') : ''].filter(Boolean)
|
disabledUsb.value = [msd.enabled ? t('hidGuide.msd') : '', network.enabled ? t('hidGuide.network') : '', audio.enabled ? t('hidGuide.audio') : ''].filter(Boolean)
|
||||||
}
|
}
|
||||||
@@ -141,7 +151,7 @@ onMounted(load)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<HidDriverForm v-if="!applied" v-model="draft" :locked="busy || !loaded" @valid="valid = $event" />
|
<HidDriverForm v-if="!applied" v-model="draft" :platform="systemStore.platform" :locked="busy || !loaded" @valid="valid = $event" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<HidWiringDiagram :backend="draft.backend" />
|
<HidWiringDiagram :backend="draft.backend" />
|
||||||
<HidDeviceOverview :hid="store.hid" :status="status" :bluetooth="bluetooth" :error="statusError" />
|
<HidDeviceOverview :hid="store.hid" :status="status" :bluetooth="bluetooth" :error="statusError" />
|
||||||
|
|||||||
@@ -6,19 +6,20 @@ import { Input } from '@/components/ui/input'
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
||||||
import { Cable, RefreshCw } from 'lucide-vue-next'
|
import { Cable, RefreshCw } from 'lucide-vue-next'
|
||||||
import { configApi } from '@/api'
|
import { configApi, type PlatformCapabilities } from '@/api'
|
||||||
import { request } from '@/api/request'
|
import { request } from '@/api/request'
|
||||||
import type { BluetoothAdapter } from '@/types/bluetooth'
|
import type { BluetoothAdapter } from '@/types/bluetooth'
|
||||||
import { selectDevice, validName, type HidSelection, type Driver } from '@/lib/hidGuide'
|
import { selectDevice, validName, type HidSelection, type Driver } from '@/lib/hidGuide'
|
||||||
import HidWiringDiagram from './HidWiringDiagram.vue'
|
import HidWiringDiagram from './HidWiringDiagram.vue'
|
||||||
const props = defineProps<{ modelValue: HidSelection; locked?: boolean }>()
|
const props = defineProps<{ modelValue: HidSelection; platform: PlatformCapabilities | null; locked?: boolean }>()
|
||||||
const emit = defineEmits<{ 'update:modelValue': [HidSelection]; valid: [boolean] }>()
|
const emit = defineEmits<{ 'update:modelValue': [HidSelection]; valid: [boolean] }>()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
||||||
const options = ref<Array<{ value: string; label: string }>>([])
|
const options = ref<Array<{ value: string; label: string }>>([])
|
||||||
const loading = ref(false), error = ref(''), missing = ref('')
|
const loading = ref(false), error = ref(''), missing = ref('')
|
||||||
let generation = 0
|
let generation = 0
|
||||||
const drivers: Driver[] = ['otg', 'ch9329', 'bluetooth', 'none']
|
const drivers = computed(() => (['otg', 'ch9329', 'bluetooth', 'none'] as Driver[])
|
||||||
|
.filter(driver => props.platform?.hid.backends.includes(driver)))
|
||||||
const selected = computed(() => props.modelValue.backend === 'otg' ? props.modelValue.otg_udc : props.modelValue.backend === 'ch9329' ? props.modelValue.ch9329_port : props.modelValue.bluetooth.adapter)
|
const selected = computed(() => props.modelValue.backend === 'otg' ? props.modelValue.otg_udc : props.modelValue.backend === 'ch9329' ? props.modelValue.ch9329_port : props.modelValue.bluetooth.adapter)
|
||||||
function device(value: string) {
|
function device(value: string) {
|
||||||
const draft = { ...props.modelValue, bluetooth: { ...props.modelValue.bluetooth } }
|
const draft = { ...props.modelValue, bluetooth: { ...props.modelValue.bluetooth } }
|
||||||
@@ -31,7 +32,7 @@ async function refresh() {
|
|||||||
const own = ++generation
|
const own = ++generation
|
||||||
options.value = []; error.value = ''; missing.value = ''; loading.value = true
|
options.value = []; error.value = ''; missing.value = ''; loading.value = true
|
||||||
try {
|
try {
|
||||||
if (props.modelValue.backend === 'none') return
|
if (!drivers.value.includes(props.modelValue.backend) || props.modelValue.backend === 'none') return
|
||||||
let next: typeof options.value
|
let next: typeof options.value
|
||||||
if (props.modelValue.backend === 'bluetooth') {
|
if (props.modelValue.backend === 'bluetooth') {
|
||||||
next = (await request<BluetoothAdapter[]>('/hid/bluetooth/adapters')).map(a => ({ value: a.name, label: `${a.name} · ${a.address}` }))
|
next = (await request<BluetoothAdapter[]>('/hid/bluetooth/adapters')).map(a => ({ value: a.name, label: `${a.name} · ${a.address}` }))
|
||||||
@@ -47,11 +48,11 @@ async function refresh() {
|
|||||||
} catch (e) { if (own === generation) error.value = e instanceof Error ? e.message : String(e) }
|
} catch (e) { if (own === generation) error.value = e instanceof Error ? e.message : String(e) }
|
||||||
finally { if (own === generation) loading.value = false }
|
finally { if (own === generation) loading.value = false }
|
||||||
}
|
}
|
||||||
const valid = computed(() => props.modelValue.backend === 'none' || (!loading.value && !error.value && options.value.some(d => d.value === selected.value)
|
const valid = computed(() => drivers.value.includes(props.modelValue.backend) && (props.modelValue.backend === 'none' || (!loading.value && !error.value && options.value.some(d => d.value === selected.value)
|
||||||
&& (props.modelValue.backend !== 'bluetooth' || validName(props.modelValue.bluetooth.name))
|
&& (props.modelValue.backend !== 'bluetooth' || validName(props.modelValue.bluetooth.name))
|
||||||
&& (props.modelValue.backend !== 'ch9329' || [9600, 19200, 38400, 57600, 115200].includes(props.modelValue.ch9329_baudrate))))
|
&& (props.modelValue.backend !== 'ch9329' || [9600, 19200, 38400, 57600, 115200].includes(props.modelValue.ch9329_baudrate)))))
|
||||||
watch(valid, value => emit('valid', value), { immediate: true })
|
watch(valid, value => emit('valid', value), { immediate: true })
|
||||||
watch(() => props.modelValue.backend, refresh, { immediate: true })
|
watch(() => [props.modelValue.backend, drivers.value.includes(props.modelValue.backend)], refresh, { immediate: true })
|
||||||
onUnmounted(() => generation++)
|
onUnmounted(() => generation++)
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -742,6 +742,7 @@ export default {
|
|||||||
httpsManualRedirectDesc: 'HTTPS with a self-signed certificate requires browser approval. Click the link and choose "Proceed" on the security warning.',
|
httpsManualRedirectDesc: 'HTTPS with a self-signed certificate requires browser approval. Click the link and choose "Proceed" on the security warning.',
|
||||||
onlineUpgrade: 'Online Upgrade',
|
onlineUpgrade: 'Online Upgrade',
|
||||||
onlineUpgradeDesc: 'Check and upgrade One-KVM',
|
onlineUpgradeDesc: 'Check and upgrade One-KVM',
|
||||||
|
onlineUpgradeUnsupported: 'Online upgrades are not supported on this platform. Download the new version and replace the program manually.',
|
||||||
updateChannel: 'Update Channel',
|
updateChannel: 'Update Channel',
|
||||||
currentVersion: 'Current Version',
|
currentVersion: 'Current Version',
|
||||||
latestVersion: 'Latest Version',
|
latestVersion: 'Latest Version',
|
||||||
|
|||||||
@@ -741,6 +741,7 @@ export default {
|
|||||||
httpsManualRedirectDesc: 'HTTPS 自签名证书需要在浏览器中手动接受,点击链接后在安全警告页选择"继续访问"即可。',
|
httpsManualRedirectDesc: 'HTTPS 自签名证书需要在浏览器中手动接受,点击链接后在安全警告页选择"继续访问"即可。',
|
||||||
onlineUpgrade: '在线升级',
|
onlineUpgrade: '在线升级',
|
||||||
onlineUpgradeDesc: '检查并升级 One-KVM',
|
onlineUpgradeDesc: '检查并升级 One-KVM',
|
||||||
|
onlineUpgradeUnsupported: '当前平台暂不支持在线升级,请下载新版本并手动替换程序。',
|
||||||
updateChannel: '升级通道',
|
updateChannel: '升级通道',
|
||||||
currentVersion: '当前版本',
|
currentVersion: '当前版本',
|
||||||
latestVersion: '最新版本',
|
latestVersion: '最新版本',
|
||||||
|
|||||||
@@ -260,6 +260,11 @@ export const useSystemStore = defineStore('system', () => {
|
|||||||
|
|
||||||
async function fetchMsdState() {
|
async function fetchMsdState() {
|
||||||
try {
|
try {
|
||||||
|
if (!platform.value) await fetchSystemInfo()
|
||||||
|
if (!platform.value?.msd.available) {
|
||||||
|
msd.value = null
|
||||||
|
return null
|
||||||
|
}
|
||||||
const result = await msdApi.status()
|
const result = await msdApi.status()
|
||||||
msd.value = {
|
msd.value = {
|
||||||
available: result.available,
|
available: result.available,
|
||||||
@@ -306,12 +311,13 @@ export const useSystemStore = defineStore('system', () => {
|
|||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const systemInfoRequest = fetchSystemInfo()
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchSystemInfo(),
|
systemInfoRequest,
|
||||||
fetchStreamState().catch(() => null),
|
fetchStreamState().catch(() => null),
|
||||||
fetchHidState().catch(() => null),
|
fetchHidState().catch(() => null),
|
||||||
fetchAtxState().catch(() => null),
|
fetchAtxState().catch(() => null),
|
||||||
fetchMsdState().catch(() => null),
|
systemInfoRequest.then(() => fetchMsdState()).catch(() => null),
|
||||||
])
|
])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e instanceof Error ? e.message : 'Failed to fetch states'
|
error.value = e instanceof Error ? e.message : 'Failed to fetch states'
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ const unifiedAudio = getUnifiedAudio()
|
|||||||
const microphone = getMicrophone()
|
const microphone = getMicrophone()
|
||||||
const uacEnabled = ref(false)
|
const uacEnabled = ref(false)
|
||||||
const microphoneTransferEnabled = computed(() => (
|
const microphoneTransferEnabled = computed(() => (
|
||||||
uacEnabled.value && configStore.hid?.backend === HidBackend.Otg
|
systemStore.platform?.otg.available === true
|
||||||
|
&& uacEnabled.value && configStore.hid?.backend === HidBackend.Otg
|
||||||
))
|
))
|
||||||
const videoSession = useVideoSession()
|
const videoSession = useVideoSession()
|
||||||
|
|
||||||
@@ -3124,6 +3125,11 @@ function handleToggleMouseMode() {
|
|||||||
|
|
||||||
async function refreshUacAvailability() {
|
async function refreshUacAvailability() {
|
||||||
try {
|
try {
|
||||||
|
if (!systemStore.platform) await systemStore.fetchSystemInfo()
|
||||||
|
if (!systemStore.platform?.otg.available) {
|
||||||
|
uacEnabled.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
const uacConfig = await uacApi.get()
|
const uacConfig = await uacApi.get()
|
||||||
uacEnabled.value = uacConfig.enabled
|
uacEnabled.value = uacConfig.enabled
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -172,6 +172,10 @@ const consoleLayoutOptions: Array<{
|
|||||||
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
||||||
|
|
||||||
const isWindows = computed(() => systemStore.platform?.mode === 'windows')
|
const isWindows = computed(() => systemStore.platform?.mode === 'windows')
|
||||||
|
const otgSupported = computed(() => systemStore.platform?.otg.available === true)
|
||||||
|
const msdSupported = computed(() => systemStore.platform?.msd.available === true)
|
||||||
|
const usbResetSupported = computed(() => systemStore.platform?.mode === 'linux')
|
||||||
|
const onlineUpgradeSupported = computed(() => systemStore.platform?.mode === 'linux')
|
||||||
|
|
||||||
const activeSection = ref<SettingsSectionId>('appearance')
|
const activeSection = ref<SettingsSectionId>('appearance')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -903,6 +907,7 @@ function otgCheckHint(item: OtgSelfCheckItem): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runOtgSelfCheck() {
|
async function runOtgSelfCheck() {
|
||||||
|
if (!otgSupported.value) return
|
||||||
otgSelfCheckLoading.value = true
|
otgSelfCheckLoading.value = true
|
||||||
otgSelfCheckError.value = ''
|
otgSelfCheckError.value = ''
|
||||||
try {
|
try {
|
||||||
@@ -988,6 +993,10 @@ const usbResetTarget = ref<import('@/api').UsbDeviceInfo | null>(null)
|
|||||||
const usbResetLoading = ref(false)
|
const usbResetLoading = ref(false)
|
||||||
|
|
||||||
async function fetchUsbDevices() {
|
async function fetchUsbDevices() {
|
||||||
|
if (!usbResetSupported.value) {
|
||||||
|
usbDevices.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
usbDevicesLoading.value = true
|
usbDevicesLoading.value = true
|
||||||
usbDevicesError.value = ''
|
usbDevicesError.value = ''
|
||||||
try {
|
try {
|
||||||
@@ -1000,7 +1009,7 @@ async function fetchUsbDevices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function confirmUsbReset() {
|
async function confirmUsbReset() {
|
||||||
if (!usbResetTarget.value) return
|
if (!usbResetSupported.value || !usbResetTarget.value) return
|
||||||
usbResetLoading.value = true
|
usbResetLoading.value = true
|
||||||
try {
|
try {
|
||||||
await usbApi.resetDevice(usbResetTarget.value.bus_num, usbResetTarget.value.dev_num)
|
await usbApi.resetDevice(usbResetTarget.value.bus_num, usbResetTarget.value.dev_num)
|
||||||
@@ -1521,7 +1530,9 @@ async function saveConfig() {
|
|||||||
saveError.value = error instanceof Error ? error.message : t('api.operationFailedDesc')
|
saveError.value = error instanceof Error ? error.message : t('api.operationFailedDesc')
|
||||||
if (activeSection.value === 'hid') {
|
if (activeSection.value === 'hid') {
|
||||||
await loadConfig().catch(() => undefined)
|
await loadConfig().catch(() => undefined)
|
||||||
otgNetworkStatus.value = await otgNetworkApi.status().catch(() => null)
|
otgNetworkStatus.value = otgSupported.value
|
||||||
|
? await otgNetworkApi.status().catch(() => null)
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -1543,15 +1554,17 @@ async function loadConfig() {
|
|||||||
configStore.refreshVideo(),
|
configStore.refreshVideo(),
|
||||||
configStore.refreshStream(),
|
configStore.refreshStream(),
|
||||||
configStore.refreshHid(),
|
configStore.refreshHid(),
|
||||||
configStore.refreshMsd(),
|
msdSupported.value ? configStore.refreshMsd() : Promise.resolve(null),
|
||||||
otgNetworkApi.get().catch(() => ({
|
otgSupported.value ? otgNetworkApi.get().catch(() => ({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
driver_mode: 'ncm' as const,
|
driver_mode: 'ncm' as const,
|
||||||
bridge_interface: '',
|
bridge_interface: '',
|
||||||
host_mac: '',
|
host_mac: '',
|
||||||
device_mac: '',
|
device_mac: '',
|
||||||
})),
|
})) : Promise.resolve(null),
|
||||||
uacApi.get().catch(() => ({ enabled: false, sample_rate: 48000, channels: 2 })),
|
otgSupported.value
|
||||||
|
? uacApi.get().catch(() => ({ enabled: false, sample_rate: 48000, channels: 2 }))
|
||||||
|
: Promise.resolve(null),
|
||||||
])
|
])
|
||||||
|
|
||||||
config.value = {
|
config.value = {
|
||||||
@@ -1572,14 +1585,14 @@ async function loadConfig() {
|
|||||||
consumer: hid.otg_functions?.consumer ?? true,
|
consumer: hid.otg_functions?.consumer ?? true,
|
||||||
} as OtgHidFunctions,
|
} as OtgHidFunctions,
|
||||||
hid_otg_keyboard_leds: hid.otg_keyboard_leds ?? false,
|
hid_otg_keyboard_leds: hid.otg_keyboard_leds ?? false,
|
||||||
msd_enabled: msd.enabled || false,
|
msd_enabled: msd?.enabled || false,
|
||||||
msd_dir: msd.msd_dir || '',
|
msd_dir: msd?.msd_dir || '',
|
||||||
msd_flash_inquiry_string: msd.flash_inquiry_string || 'One-KVM Virtual Flash',
|
msd_flash_inquiry_string: msd?.flash_inquiry_string || 'One-KVM Virtual Flash',
|
||||||
msd_cdrom_inquiry_string: msd.cdrom_inquiry_string || 'One-KVM Virtual CD-ROM',
|
msd_cdrom_inquiry_string: msd?.cdrom_inquiry_string || 'One-KVM Virtual CD-ROM',
|
||||||
otg_network_enabled: otgNetwork.enabled,
|
otg_network_enabled: otgNetwork?.enabled ?? false,
|
||||||
otg_network_driver: otgNetwork.driver_mode,
|
otg_network_driver: otgNetwork?.driver_mode ?? 'ncm',
|
||||||
uac_enabled: uac.enabled,
|
uac_enabled: uac?.enabled ?? false,
|
||||||
otg_network_interface: otgNetwork.bridge_interface,
|
otg_network_interface: otgNetwork?.bridge_interface ?? '',
|
||||||
encoder_backend: stream.encoder || 'auto',
|
encoder_backend: stream.encoder || 'auto',
|
||||||
stun_server: stream.stun_server || '',
|
stun_server: stream.stun_server || '',
|
||||||
turn_server: stream.turn_server || '',
|
turn_server: stream.turn_server || '',
|
||||||
@@ -1607,7 +1620,9 @@ async function loadConfig() {
|
|||||||
} else {
|
} else {
|
||||||
clearCh9329DescriptorState()
|
clearCh9329DescriptorState()
|
||||||
}
|
}
|
||||||
otgNetworkStatus.value = await otgNetworkApi.status().catch(() => null)
|
otgNetworkStatus.value = otgSupported.value
|
||||||
|
? await otgNetworkApi.status().catch(() => null)
|
||||||
|
: null
|
||||||
await nextTick()
|
await nextTick()
|
||||||
hidFeatureBaseline.value = hidFeatureSnapshot()
|
hidFeatureBaseline.value = hidFeatureSnapshot()
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1623,6 +1638,11 @@ async function loadDeviceConfig() {
|
|||||||
|
|
||||||
async function loadOtgNetworkInterfaces() {
|
async function loadOtgNetworkInterfaces() {
|
||||||
otgNetworkInterfacesError.value = ''
|
otgNetworkInterfacesError.value = ''
|
||||||
|
if (!otgSupported.value) {
|
||||||
|
otgNetworkInterfaces.value = []
|
||||||
|
otgNetworkInterfacesLoaded.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
otgNetworkInterfaces.value = await otgNetworkApi.interfaces()
|
otgNetworkInterfaces.value = await otgNetworkApi.interfaces()
|
||||||
otgNetworkInterfacesLoaded.value = true
|
otgNetworkInterfacesLoaded.value = true
|
||||||
@@ -2331,6 +2351,7 @@ function startUpdatePolling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startOnlineUpgrade() {
|
async function startOnlineUpgrade() {
|
||||||
|
if (!onlineUpgradeSupported.value) return
|
||||||
try {
|
try {
|
||||||
updateSawRestarting.value = false
|
updateSawRestarting.value = false
|
||||||
updateSawRequestFailure.value = false
|
updateSawRequestFailure.value = false
|
||||||
@@ -3478,7 +3499,7 @@ watch(isWindows, () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card v-if="otgSupported">
|
||||||
<CardHeader class="flex flex-row items-start justify-between space-y-0">
|
<CardHeader class="flex flex-row items-start justify-between space-y-0">
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<CardTitle>{{ t('settings.otgSelfCheck.title') }}</CardTitle>
|
<CardTitle>{{ t('settings.otgSelfCheck.title') }}</CardTitle>
|
||||||
@@ -3691,7 +3712,7 @@ watch(isWindows, () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card v-if="usbResetSupported">
|
||||||
<CardHeader class="flex flex-row items-start justify-between space-y-0">
|
<CardHeader class="flex flex-row items-start justify-between space-y-0">
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<CardTitle>{{ t('settings.usbDevices.title') }}</CardTitle>
|
<CardTitle>{{ t('settings.usbDevices.title') }}</CardTitle>
|
||||||
@@ -3766,7 +3787,7 @@ watch(isWindows, () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- USB Reset Confirmation Dialog -->
|
<!-- USB Reset Confirmation Dialog -->
|
||||||
<AlertDialog :open="usbResetTarget != null">
|
<AlertDialog v-if="usbResetSupported" :open="usbResetTarget != null">
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>{{ t('settings.usbDevices.resetConfirmTitle') }}</AlertDialogTitle>
|
<AlertDialogTitle>{{ t('settings.usbDevices.resetConfirmTitle') }}</AlertDialogTitle>
|
||||||
@@ -5375,7 +5396,10 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end gap-2">
|
<p v-if="!onlineUpgradeSupported" class="text-sm text-muted-foreground">
|
||||||
|
{{ t('settings.onlineUpgradeUnsupported') }}
|
||||||
|
</p>
|
||||||
|
<div v-else class="flex justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
:disabled="updateRunning || !updateOverview?.upgrade_available"
|
:disabled="updateRunning || !updateOverview?.upgrade_available"
|
||||||
@click="startOnlineUpgrade"
|
@click="startOnlineUpgrade"
|
||||||
|
|||||||
@@ -699,7 +699,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
<div v-else-if="step === 3" key="step3" class="space-y-4">
|
<div v-else-if="step === 3" key="step3" class="space-y-4">
|
||||||
<h3 class="text-lg font-medium text-center">{{ t('setup.stepHid') }}</h3>
|
<h3 class="text-lg font-medium text-center">{{ t('setup.stepHid') }}</h3>
|
||||||
|
|
||||||
<HidDriverForm v-model="hidSelection" @valid="hidSelectionValid = $event" />
|
<HidDriverForm v-model="hidSelection" :platform="platform" @valid="hidSelectionValid = $event" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 4: Extensions Settings -->
|
<!-- Step 4: Extensions Settings -->
|
||||||
|
|||||||
Reference in New Issue
Block a user