feat: 服务器资源趋势图表(Chart.js本地化+CPU/内存/磁盘趋势)
This commit is contained in:
+34
-2
@@ -1,5 +1,5 @@
|
||||
/* vps-manager 前端应用(Vue 3 组件化) */
|
||||
const { createApp, reactive, ref, computed, onMounted, watch } = Vue;
|
||||
const { createApp, reactive, ref, computed, onMounted, watch, nextTick } = Vue;
|
||||
const Api = window.VpsApi;
|
||||
const Fmt = window.VpsFmt;
|
||||
|
||||
@@ -156,7 +156,7 @@ async function viewServer(a) {
|
||||
store.view = 'servers';
|
||||
try {
|
||||
const [metrics, info, checks] = await Promise.all([
|
||||
Api.get('/monitor/' + a.id + '/metrics'),
|
||||
Api.get('/monitor/' + a.id + '/metrics?limit=200'),
|
||||
Api.get('/monitor/' + a.id + '/info'),
|
||||
Api.get('/monitor/' + a.id + '/security'),
|
||||
]);
|
||||
@@ -494,6 +494,10 @@ const ServersView = {
|
||||
<div>公网 IP:{{ m.info.public_ip || '—' }}</div><div>状态:{{ m.info.status }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="m.metrics && m.metrics.length > 1" class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4">
|
||||
<h3 class="font-semibold mb-2 text-sm">资源趋势(最近 {{ m.metrics.length }} 次采样)</h3>
|
||||
<canvas id="metricsChart" height="90"></canvas>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4">
|
||||
<h3 class="font-semibold mb-2 text-sm">安全检查</h3>
|
||||
<div class="space-y-2">
|
||||
@@ -513,6 +517,34 @@ const ServersView = {
|
||||
setup() {
|
||||
const m = computed(() => store.serverMetrics);
|
||||
const latest = computed(() => (m.value.metrics && m.value.metrics.length) ? m.value.metrics[0] : null);
|
||||
let chartInstance = null;
|
||||
function renderChart() {
|
||||
const canvas = document.getElementById('metricsChart');
|
||||
if (!canvas || !window.Chart) return;
|
||||
if (!m.value.metrics || m.value.metrics.length < 2) return;
|
||||
if (chartInstance) { chartInstance.destroy(); chartInstance = null; }
|
||||
const data = [...m.value.metrics].reverse();
|
||||
const labels = data.map(p => new Date(p.ts).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }));
|
||||
chartInstance = new Chart(canvas, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [
|
||||
{ label: 'CPU %', data: data.map(p => p.cpu_pct), borderColor: '#3b82f6', backgroundColor: 'rgba(59,130,246,0.1)', tension: 0.3, pointRadius: 0 },
|
||||
{ label: '内存 %', data: data.map(p => p.mem_pct), borderColor: '#10b981', backgroundColor: 'rgba(16,185,129,0.1)', tension: 0.3, pointRadius: 0 },
|
||||
{ label: '磁盘 %', data: data.map(p => p.disk_pct), borderColor: '#f59e0b', backgroundColor: 'rgba(245,158,11,0.1)', tension: 0.3, pointRadius: 0 },
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
scales: { y: { min: 0, max: 100, ticks: { callback: v => v + '%' } } },
|
||||
plugins: { legend: { position: 'top' } },
|
||||
},
|
||||
});
|
||||
}
|
||||
watch(m, () => { nextTick(renderChart); }, { deep: true });
|
||||
onMounted(() => { nextTick(renderChart); });
|
||||
return { store, Fmt, m, latest };
|
||||
},
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user