Live log view over WebSocket, full log, scroll pinned to bottom

This commit is contained in:
2026-09-23 17:31:13 +00:00
parent 2c4baf693c
commit 603884c5a2
3 changed files with 64 additions and 52 deletions
+37 -30
View File
@@ -306,9 +306,8 @@
<div class="diag-log-header">
<span class="diag-label">Application log</span>
<button class="diag-refresh-btn" @click="refreshLog">Refresh</button>
</div>
<pre class="diag-log">{{ appLog }}</pre>
<pre ref="logEl" class="diag-log" @scroll="onLogScroll">{{ appLog }}</pre>
</section>
</div>
</div>
@@ -317,11 +316,11 @@
</template>
<script setup lang="ts">
import { ref, watch, computed, onMounted, onUnmounted } from "vue"
import { ref, watch, computed, onMounted, onUnmounted, nextTick } from "vue"
import { useRouter, useRoute } from "vue-router"
import { navAttrs } from "../composables/useKeyboardNavigation"
import logoUrl from "../assets/mediahive.webp"
import { replaceRoots, pickFolderAndAddRoot, fetchPlayers, getLog } from "../api"
import { replaceRoots, pickFolderAndAddRoot, fetchPlayers } from "../api"
import type { PlayerInfo } from "../api"
import HexKeyboard from "./HexKeyboard.vue"
import {
@@ -425,15 +424,38 @@ async function refreshPlayers() {
}
const appLog = ref("")
let logFetched = false
const logEl = ref<HTMLElement | null>(null)
let logSocket: WebSocket | null = null
let pinnedToBottom = true
async function refreshLog() {
try {
appLog.value = await getLog()
} catch (e) {
console.error("Failed to fetch log:", e)
appLog.value = "Failed to load log."
function onLogScroll() {
const el = logEl.value
if (!el) return
pinnedToBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 48
}
function connectLogSocket() {
if (logSocket) return
const proto = location.protocol === "https:" ? "wss" : "ws"
const ws = new WebSocket(`${proto}://${location.host}/api/log/ws`)
logSocket = ws
pinnedToBottom = true
ws.onmessage = async (ev) => {
appLog.value = String(ev.data)
await nextTick()
const el = logEl.value
if (el && pinnedToBottom) el.scrollTop = el.scrollHeight
}
ws.onclose = () => {
if (logSocket === ws) logSocket = null
if (showSettings.value) setTimeout(connectLogSocket, 3000)
}
}
function disconnectLogSocket() {
const ws = logSocket
logSocket = null
ws?.close()
}
async function removeRoot(rootId: string) {
@@ -465,10 +487,9 @@ async function addRoot() {
watch(showSettings, (visible) => {
if (visible) {
void refreshPlayers()
if (!logFetched) {
logFetched = true
void refreshLog()
}
connectLogSocket()
} else {
disconnectLogSocket()
}
})
@@ -582,6 +603,7 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener("keydown", handleKeydown)
window.removeEventListener("mediahive:gamepad-action", onGamepadAction)
disconnectLogSocket()
})
</script>
@@ -912,21 +934,6 @@ onUnmounted(() => {
margin-bottom: 6px;
}
.diag-refresh-btn {
padding: 4px 12px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
color: var(--text-primary);
font-size: 0.8rem;
cursor: pointer;
transition: background 0.2s;
}
.diag-refresh-btn:hover {
background: rgba(255, 255, 255, 0.15);
}
.diag-log {
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 0.75rem;