(function () { 'use strict'; const asset = 'modules/servers/vpsmanager/assets/chart-4.4.8.scoped.js'; const unavailable = 'Las métricas no están disponibles temporalmente.'; const colors = ['#176aa3', '#bd5623', '#338345']; function scaled(value, base, units) { if (value === null || !Number.isFinite(value)) return 'Sin datos'; let i = 0; while (Math.abs(value) >= base && i < units.length - 1) { value /= base; i++; } return value.toLocaleString('es-ES', { maximumFractionDigits: 2 }) + ' ' + units[i]; } function format(kind, value) { if (kind === 'memory') return scaled(value, 1024, ['B', 'KiB', 'MiB', 'GiB', 'TiB']); if (kind === 'network') return scaled(value, 1000, ['bit/s', 'Kbit/s', 'Mbit/s', 'Gbit/s']); return value === null ? 'Sin datos' : value.toLocaleString('es-ES', { maximumFractionDigits: 2 }); } function datasets(kind, series) { return Object.entries(series).map(([label, points], i) => ({ label, // The server keeps rates in bytes/s. Conversion happens exactly here. data: points.map(p => ({ x: p.x, y: p.y === null ? null : (kind === 'network' ? p.y * 8 : p.y) })), borderColor: colors[i % colors.length], backgroundColor: colors[i % colors.length], borderWidth: 2, pointRadius: 0, pointHitRadius: 8, spanGaps: false, tension: 0 })); } function summary(kind, data, metrics) { const times = data.flatMap(d => d.data.map(p => p.x)); if (!times.length) return unavailable; const lastTime = times.reduce((a, b) => Math.max(a, b), 0); const values = data.map(d => { const point = d.data.find(p => p.x === lastTime); return d.label + ': ' + format(kind, point ? point.y : null); }); if (kind === 'memory') { const percentage = metrics.memoryPercent.find(p => p.x === lastTime); values.push('Uso: ' + (percentage && percentage.y !== null ? percentage.y.toFixed(1) + ' %' : 'Sin datos')); } return values.join(' · ') + ' · ' + new Date(lastTime).toLocaleString('es-ES'); } function start(ChartClass) { document.querySelectorAll('.vpsmanager-metrics[data-metrics]').forEach(root => { if (root.dataset.initialized || !root.dataset.metrics) return; root.dataset.initialized = 'true'; let metrics; try { metrics = JSON.parse(root.dataset.metrics); } catch (_) { return; } root.querySelectorAll('[data-chart]').forEach(card => { const kind = card.dataset.chart; const data = datasets(kind, metrics[kind]); const text = card.querySelector('[data-summary]'); const canvas = card.querySelector('canvas'); if (!ChartClass || !data.some(d => d.data.some(p => p.y !== null))) { text.textContent = unavailable; canvas.parentElement.hidden = true; return; } text.textContent = summary(kind, data, metrics); try { new ChartClass(canvas, { type: 'line', data: { datasets: data }, options: { responsive: true, maintainAspectRatio: false, animation: false, parsing: false, interaction: { mode: 'nearest', intersect: false }, scales: { x: { type: 'linear', ticks: { maxTicksLimit: 6, callback: v => new Date(v).toLocaleString('es-ES', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) } }, y: { beginAtZero: true, ticks: { callback: v => format(kind, v) } } }, plugins: { tooltip: { callbacks: { title: items => items.length ? new Date(items[0].parsed.x).toLocaleString('es-ES') : '', label: ctx => ctx.dataset.label + ': ' + format(kind, ctx.parsed.y) } } } } }); } catch (_) { text.textContent = unavailable; canvas.parentElement.hidden = true; } }); }); } function boot() { // Reuse an installed compatible Chart.js; otherwise use the local isolated build. if (window.Chart && /^4\./.test(window.Chart.version || '')) { start(window.Chart); return; } if (window.O6HVpsChart) { start(window.O6HVpsChart); return; } const script = document.createElement('script'); script.src = asset; script.onload = () => start(window.O6HVpsChart); script.onerror = () => start(null); document.head.appendChild(script); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true }); else boot(); }());