mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-07-29 07:31:45 +08:00
del: MJPEG /HTTTP 视频模式删除网页连接统计功能
This commit is contained in:
@@ -66,6 +66,7 @@ const props = defineProps<{
|
|||||||
showTerminal?: boolean
|
showTerminal?: boolean
|
||||||
showComputerUse?: boolean
|
showComputerUse?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
const showStats = computed(() => (props.videoMode ?? 'mjpeg') !== 'mjpeg')
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'toggleFullscreen'): void
|
(e: 'toggleFullscreen'): void
|
||||||
@@ -196,6 +197,7 @@ const RIGHT_FIXED_PX = 120
|
|||||||
const collapsibleItems = computed(() => {
|
const collapsibleItems = computed(() => {
|
||||||
const items = ITEM_SPECS.slice(3).filter(item => {
|
const items = ITEM_SPECS.slice(3).filter(item => {
|
||||||
if (item.id === 'msd' && !showMsd.value) return false
|
if (item.id === 'msd' && !showMsd.value) return false
|
||||||
|
if (item.id === 'stats' && !showStats.value) return false
|
||||||
if (item.id === 'terminal' && props.showTerminal === false) return false
|
if (item.id === 'terminal' && props.showTerminal === false) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -244,6 +246,12 @@ const isVisible = (id: CollapsibleItem) => visibleSet.value.has(id)
|
|||||||
const hasOverflow = computed(() => {
|
const hasOverflow = computed(() => {
|
||||||
return collapsibleItems.value.some(i => !visibleSet.value.has(i.id))
|
return collapsibleItems.value.some(i => !visibleSet.value.has(i.id))
|
||||||
})
|
})
|
||||||
|
const hasLeftOverflow = computed(() => {
|
||||||
|
return collapsibleItems.value.some(i => i.side === 'left' && !visibleSet.value.has(i.id))
|
||||||
|
})
|
||||||
|
const hasRightOverflow = computed(() => {
|
||||||
|
return collapsibleItems.value.some(i => i.side === 'right' && !visibleSet.value.has(i.id))
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -469,10 +477,10 @@ const hasOverflow = computed(() => {
|
|||||||
{{ t('actionbar.paste') }}
|
{{ t('actionbar.paste') }}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
<DropdownMenuSeparator v-if="(!isVisible('msd') || !isVisible('atx') || !isVisible('paste')) && (!isVisible('stats') || (props.showTerminal !== false && !isVisible('terminal')) || !isVisible('settings'))" />
|
<DropdownMenuSeparator v-if="hasLeftOverflow && hasRightOverflow" />
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<DropdownMenuItem v-if="!isVisible('stats')" @click="openFromOverflow(() => emit('toggleStats'))">
|
<DropdownMenuItem v-if="showStats && !isVisible('stats')" @click="openFromOverflow(() => emit('toggleStats'))">
|
||||||
<BarChart3 class="h-4 w-4 mr-2" />
|
<BarChart3 class="h-4 w-4 mr-2" />
|
||||||
{{ t('actionbar.stats') }}
|
{{ t('actionbar.stats') }}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
videoMode: 'mjpeg' | 'h264' | 'h265' | 'vp8' | 'vp9'
|
|
||||||
// MJPEG stats
|
|
||||||
mjpegFps?: number
|
|
||||||
wsLatency?: number
|
|
||||||
// WebRTC stats
|
|
||||||
webrtcStats?: WebRTCStats
|
webrtcStats?: WebRTCStats
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -51,9 +46,6 @@ let lastBytesReceived = 0
|
|||||||
let lastPacketsLost = 0
|
let lastPacketsLost = 0
|
||||||
let lastTimestamp = 0
|
let lastTimestamp = 0
|
||||||
|
|
||||||
// Is WebRTC mode
|
|
||||||
const isWebRTC = computed(() => props.videoMode !== 'mjpeg')
|
|
||||||
|
|
||||||
function formatTime(ts: number): string {
|
function formatTime(ts: number): string {
|
||||||
const date = new Date(ts * 1000)
|
const date = new Date(ts * 1000)
|
||||||
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||||
@@ -256,14 +248,13 @@ function addDataPoint() {
|
|||||||
timestamps.value.shift()
|
timestamps.value.shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWebRTC.value && props.webrtcStats) {
|
if (props.webrtcStats) {
|
||||||
const jitter = (props.webrtcStats.jitter || 0) * 1000
|
const jitter = (props.webrtcStats.jitter || 0) * 1000
|
||||||
jitterHistory.value.push(jitter)
|
jitterHistory.value.push(jitter)
|
||||||
|
|
||||||
const rtt = (props.webrtcStats.roundTripTime || 0) * 1000
|
const rtt = (props.webrtcStats.roundTripTime || 0) * 1000
|
||||||
delayHistory.value.push(rtt)
|
delayHistory.value.push(rtt)
|
||||||
|
|
||||||
// Packet loss delta
|
|
||||||
const currentLost = props.webrtcStats.packetsLost || 0
|
const currentLost = props.webrtcStats.packetsLost || 0
|
||||||
const lostDelta = lastPacketsLost > 0 ? Math.max(0, currentLost - lastPacketsLost) : 0
|
const lostDelta = lastPacketsLost > 0 ? Math.max(0, currentLost - lastPacketsLost) : 0
|
||||||
lastPacketsLost = currentLost
|
lastPacketsLost = currentLost
|
||||||
@@ -284,11 +275,10 @@ function addDataPoint() {
|
|||||||
lastBytesReceived = currentBytes
|
lastBytesReceived = currentBytes
|
||||||
lastTimestamp = currentTime
|
lastTimestamp = currentTime
|
||||||
} else {
|
} else {
|
||||||
// MJPEG mode
|
|
||||||
jitterHistory.value.push(0)
|
jitterHistory.value.push(0)
|
||||||
delayHistory.value.push(props.wsLatency || 0)
|
delayHistory.value.push(0)
|
||||||
packetLossHistory.value.push(0)
|
packetLossHistory.value.push(0)
|
||||||
fpsHistory.value.push(props.mjpegFps || 0)
|
fpsHistory.value.push(0)
|
||||||
bitrateHistory.value.push(0)
|
bitrateHistory.value.push(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +325,7 @@ function formatCandidateType(type: string): string {
|
|||||||
|
|
||||||
// Current stats for header display
|
// Current stats for header display
|
||||||
const currentStats = computed(() => {
|
const currentStats = computed(() => {
|
||||||
if (isWebRTC.value && props.webrtcStats) {
|
if (props.webrtcStats) {
|
||||||
const lastBitrate = bitrateHistory.value[bitrateHistory.value.length - 1]
|
const lastBitrate = bitrateHistory.value[bitrateHistory.value.length - 1]
|
||||||
const bitrate = lastBitrate !== undefined ? lastBitrate : 0
|
const bitrate = lastBitrate !== undefined ? lastBitrate : 0
|
||||||
return {
|
return {
|
||||||
@@ -356,8 +346,8 @@ const currentStats = computed(() => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
jitter: 0,
|
jitter: 0,
|
||||||
delay: props.wsLatency || 0,
|
delay: 0,
|
||||||
fps: props.mjpegFps || 0,
|
fps: 0,
|
||||||
resolution: '-',
|
resolution: '-',
|
||||||
bitrate: '0',
|
bitrate: '0',
|
||||||
packetsLost: 0,
|
packetsLost: 0,
|
||||||
@@ -422,7 +412,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<SheetTitle class="text-base">{{ t('stats.title') }}</SheetTitle>
|
<SheetTitle class="text-base">{{ t('stats.title') }}</SheetTitle>
|
||||||
<span class="text-xs px-2 py-0.5 rounded bg-slate-100 dark:bg-slate-800 text-muted-foreground">
|
<span class="text-xs px-2 py-0.5 rounded bg-slate-100 dark:bg-slate-800 text-muted-foreground">
|
||||||
{{ isWebRTC ? 'WebRTC' : 'MJPEG' }}
|
WebRTC
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
@@ -438,7 +428,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Network Stability (Jitter) -->
|
<!-- Network Stability (Jitter) -->
|
||||||
<div class="space-y-2" v-if="isWebRTC">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h4 class="text-sm font-medium">{{ t('stats.stability') }}</h4>
|
<h4 class="text-sm font-medium">{{ t('stats.stability') }}</h4>
|
||||||
</div>
|
</div>
|
||||||
@@ -462,7 +452,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Playback Delay -->
|
<!-- Playback Delay -->
|
||||||
<div class="space-y-2" v-if="isWebRTC">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h4 class="text-sm font-medium">{{ t('stats.delay') }}</h4>
|
<h4 class="text-sm font-medium">{{ t('stats.delay') }}</h4>
|
||||||
<span class="text-xs text-muted-foreground">
|
<span class="text-xs text-muted-foreground">
|
||||||
@@ -489,7 +479,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Packet Loss -->
|
<!-- Packet Loss -->
|
||||||
<div class="space-y-2" v-if="isWebRTC">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h4 class="text-sm font-medium">{{ t('stats.packetLoss') }}</h4>
|
<h4 class="text-sm font-medium">{{ t('stats.packetLoss') }}</h4>
|
||||||
<span class="text-xs text-muted-foreground">
|
<span class="text-xs text-muted-foreground">
|
||||||
@@ -543,7 +533,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Additional Stats -->
|
<!-- Additional Stats -->
|
||||||
<div class="space-y-3 pt-2 border-t border-slate-200 dark:border-slate-800" v-if="isWebRTC">
|
<div class="space-y-3 pt-2 border-t border-slate-200 dark:border-slate-800">
|
||||||
<h4 class="text-sm font-medium">{{ t('stats.additional') }}</h4>
|
<h4 class="text-sm font-medium">{{ t('stats.additional') }}</h4>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div class="rounded-lg bg-slate-50 dark:bg-slate-900/50 p-3">
|
<div class="rounded-lg bg-slate-50 dark:bg-slate-900/50 p-3">
|
||||||
|
|||||||
@@ -1101,8 +1101,6 @@ export default {
|
|||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
title: 'Connection Stats',
|
title: 'Connection Stats',
|
||||||
webrtcMode: 'WebRTC Real-time Stats',
|
|
||||||
mjpegMode: 'MJPEG Real-time Stats',
|
|
||||||
current: 'Current Status',
|
current: 'Current Status',
|
||||||
video: 'Video',
|
video: 'Video',
|
||||||
videoDesc: 'Video stream from server to client.',
|
videoDesc: 'Video stream from server to client.',
|
||||||
@@ -1122,7 +1120,6 @@ export default {
|
|||||||
fps: 'FPS',
|
fps: 'FPS',
|
||||||
resolution: 'Resolution',
|
resolution: 'Resolution',
|
||||||
packetsLost: 'Packets Lost',
|
packetsLost: 'Packets Lost',
|
||||||
wsLatency: 'WS Latency',
|
|
||||||
connection: 'Connection Info',
|
connection: 'Connection Info',
|
||||||
connectionType: 'Connection Type',
|
connectionType: 'Connection Type',
|
||||||
transport: 'Transport',
|
transport: 'Transport',
|
||||||
|
|||||||
@@ -1100,8 +1100,6 @@ export default {
|
|||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
title: '连接统计',
|
title: '连接统计',
|
||||||
webrtcMode: 'WebRTC 实时统计',
|
|
||||||
mjpegMode: 'MJPEG 实时统计',
|
|
||||||
current: '当前状态',
|
current: '当前状态',
|
||||||
video: '视频',
|
video: '视频',
|
||||||
videoDesc: '从服务器到客户端的视频流。',
|
videoDesc: '从服务器到客户端的视频流。',
|
||||||
@@ -1121,7 +1119,6 @@ export default {
|
|||||||
fps: '帧率',
|
fps: '帧率',
|
||||||
resolution: '分辨率',
|
resolution: '分辨率',
|
||||||
packetsLost: '丢包数',
|
packetsLost: '丢包数',
|
||||||
wsLatency: 'WS 延迟',
|
|
||||||
connection: '连接信息',
|
connection: '连接信息',
|
||||||
connectionType: '连接类型',
|
connectionType: '连接类型',
|
||||||
transport: '传输协议',
|
transport: '传输协议',
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ function syncMouseModeFromConfig() {
|
|||||||
const virtualKeyboardVisible = ref(false)
|
const virtualKeyboardVisible = ref(false)
|
||||||
const virtualKeyboardAttached = ref(true)
|
const virtualKeyboardAttached = ref(true)
|
||||||
const statsSheetOpen = ref(false)
|
const statsSheetOpen = ref(false)
|
||||||
|
const showConnectionStats = computed(() => videoMode.value !== 'mjpeg')
|
||||||
const virtualKeyboardConsumerEnabled = computed(() => {
|
const virtualKeyboardConsumerEnabled = computed(() => {
|
||||||
const hid = configStore.hid
|
const hid = configStore.hid
|
||||||
if (!hid) return true
|
if (!hid) return true
|
||||||
@@ -207,6 +208,18 @@ const videoStatus = computed<'connected' | 'connecting' | 'disconnected' | 'erro
|
|||||||
return 'disconnected'
|
return 'disconnected'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function openStatsSheet() {
|
||||||
|
if (showConnectionStats.value) {
|
||||||
|
statsSheetOpen.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(showConnectionStats, (canShow) => {
|
||||||
|
if (!canShow) {
|
||||||
|
statsSheetOpen.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function getResolutionShortName(width: number, height: number): string {
|
function getResolutionShortName(width: number, height: number): string {
|
||||||
if (height === 2160 || (height === 2160 && width === 4096)) return '4K'
|
if (height === 2160 || (height === 2160 && width === 4096)) return '4K'
|
||||||
if (height === 1440) return '2K'
|
if (height === 1440) return '2K'
|
||||||
@@ -2835,7 +2848,7 @@ onUnmounted(() => {
|
|||||||
:show-terminal="showTerminal"
|
:show-terminal="showTerminal"
|
||||||
:show-computer-use="showComputerUse"
|
:show-computer-use="showComputerUse"
|
||||||
@toggle-fullscreen="toggleFullscreen"
|
@toggle-fullscreen="toggleFullscreen"
|
||||||
@toggle-stats="statsSheetOpen = true"
|
@toggle-stats="openStatsSheet"
|
||||||
@toggle-virtual-keyboard="handleToggleVirtualKeyboard"
|
@toggle-virtual-keyboard="handleToggleVirtualKeyboard"
|
||||||
@toggle-mouse-mode="handleToggleMouseMode"
|
@toggle-mouse-mode="handleToggleMouseMode"
|
||||||
@update:video-mode="handleVideoModeChange"
|
@update:video-mode="handleVideoModeChange"
|
||||||
@@ -3086,10 +3099,8 @@ onUnmounted(() => {
|
|||||||
:debug-mode="false"
|
:debug-mode="false"
|
||||||
/>
|
/>
|
||||||
<StatsSheet
|
<StatsSheet
|
||||||
|
v-if="showConnectionStats"
|
||||||
v-model:open="statsSheetOpen"
|
v-model:open="statsSheetOpen"
|
||||||
:video-mode="videoMode"
|
|
||||||
:mjpeg-fps="backendFps"
|
|
||||||
:ws-latency="0"
|
|
||||||
:webrtc-stats="webrtc.stats.value"
|
:webrtc-stats="webrtc.stats.value"
|
||||||
/>
|
/>
|
||||||
<Dialog v-if="showTerminal" v-model:open="showTerminalDialog">
|
<Dialog v-if="showTerminal" v-model:open="showTerminalDialog">
|
||||||
|
|||||||
Reference in New Issue
Block a user