Files
playchat/frontend/src/components/MemoryMonitor.vue
T
colcaroli 002282a8b7 Fix MemoryMonitor blank on GPU-less deployments
gpu_memory_percent is serialized as JSON null (not omitted) when no
GPU is present, but the template checked `!== undefined`, which is
true for null too. Calling .toFixed() on that null then threw during
render, blanking the whole widget instead of just hiding the GPU row.
Only showed up on GPU-less hosts like a Lightsail instance, not on
this dev machine which has a GPU.
2026-08-18 21:48:38 +09:00

123 lines
2.7 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
interface MemoryStats {
cpu_percent: number
memory_percent: number
memory_used_mb: number
memory_total_mb: number
gpu_memory_used_mb?: number
gpu_memory_total_mb?: number
gpu_memory_percent?: number
}
const stats = ref<MemoryStats | null>(null)
let intervalId: number | null = null
const fetchMemoryStats = async () => {
try {
const response = await fetch('/api/system/memory')
stats.value = await response.json()
} catch (error) {
console.error('Failed to fetch memory stats:', error)
}
}
const formatMemory = (mb: number) => {
if (mb > 1024) {
return `${(mb / 1024).toFixed(1)}GB`
}
return `${Math.round(mb)}MB`
}
onMounted(() => {
fetchMemoryStats()
intervalId = window.setInterval(fetchMemoryStats, 10000)
})
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId)
}
})
</script>
<template>
<div class="memory-monitor" v-if="stats">
<div class="stat">
<span class="label">CPU</span>
<div class="bar">
<div class="fill" :style="{ width: stats.cpu_percent + '%' }"></div>
</div>
<span class="value">{{ stats.cpu_percent.toFixed(1) }}%</span>
</div>
<div class="stat">
<span class="label">RAM</span>
<div class="bar">
<div class="fill" :style="{ width: stats.memory_percent + '%' }"></div>
</div>
<span class="value">{{ stats.memory_percent.toFixed(1) }}% ({{ formatMemory(stats.memory_used_mb) }} / {{ formatMemory(stats.memory_total_mb) }})</span>
</div>
<div class="stat" v-if="stats.gpu_memory_percent != null">
<span class="label">GPU</span>
<div class="bar">
<div class="fill gpu" :style="{ width: stats.gpu_memory_percent + '%' }"></div>
</div>
<span class="value">{{ stats.gpu_memory_percent.toFixed(1) }}% ({{ formatMemory(stats.gpu_memory_used_mb!) }} / {{ formatMemory(stats.gpu_memory_total_mb!) }})</span>
</div>
</div>
</template>
<style scoped>
.memory-monitor {
background: #1a1a1a;
border-top: 1px solid #333;
padding: 12px;
font-size: 11px;
font-family: monospace;
gap: 8px;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.stat {
display: flex;
align-items: center;
gap: 8px;
}
.label {
width: 45px;
color: #888;
font-weight: bold;
}
.bar {
flex: 1;
height: 16px;
background: #2a2a2a;
border-radius: 3px;
border: 1px solid #444;
overflow: hidden;
}
.fill {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #8BC34A);
transition: width 0.3s ease;
}
.fill.gpu {
background: linear-gradient(90deg, #2196F3, #64B5F6);
}
.value {
width: 120px;
text-align: right;
color: #aaa;
}
</style>