Live log view over WebSocket, full log, scroll pinned to bottom
This commit is contained in:
@@ -436,14 +436,3 @@ export async function pickFolderAndAddRoot(): Promise<string | null> {
|
||||
const folder: string | null = await api.pick_folder()
|
||||
return folder
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the tail of the application log.
|
||||
*/
|
||||
export async function getLog(): Promise<string> {
|
||||
const response = await fetch("/api/log")
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load log: ${response.statusText}`)
|
||||
}
|
||||
return response.text()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user