Módulo VPSManager para WHMCS con configuración externa, acceso seguro al panel y métricas Graphite para LX.
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

93 řádky
5.0 KiB

  1. (function () {
  2. 'use strict';
  3. const asset = 'modules/servers/vpsmanager/assets/chart-4.4.8.scoped.js';
  4. const unavailable = 'Las métricas no están disponibles temporalmente.';
  5. const colors = ['#176aa3', '#bd5623', '#338345'];
  6. function scaled(value, base, units) {
  7. if (value === null || !Number.isFinite(value)) return 'Sin datos';
  8. let i = 0;
  9. while (Math.abs(value) >= base && i < units.length - 1) { value /= base; i++; }
  10. return value.toLocaleString('es-ES', { maximumFractionDigits: 2 }) + ' ' + units[i];
  11. }
  12. function format(kind, value) {
  13. if (kind === 'memory') return scaled(value, 1024, ['B', 'KiB', 'MiB', 'GiB', 'TiB']);
  14. if (kind === 'network') return scaled(value, 1000, ['bit/s', 'Kbit/s', 'Mbit/s', 'Gbit/s']);
  15. return value === null ? 'Sin datos' : value.toLocaleString('es-ES', { maximumFractionDigits: 2 });
  16. }
  17. function datasets(kind, series) {
  18. return Object.entries(series).map(([label, points], i) => ({
  19. label,
  20. // The server keeps rates in bytes/s. Conversion happens exactly here.
  21. data: points.map(p => ({ x: p.x, y: p.y === null ? null : (kind === 'network' ? p.y * 8 : p.y) })),
  22. borderColor: colors[i % colors.length], backgroundColor: colors[i % colors.length],
  23. borderWidth: 2, pointRadius: 0, pointHitRadius: 8, spanGaps: false, tension: 0
  24. }));
  25. }
  26. function summary(kind, data, metrics) {
  27. const times = data.flatMap(d => d.data.map(p => p.x));
  28. if (!times.length) return unavailable;
  29. const lastTime = times.reduce((a, b) => Math.max(a, b), 0);
  30. const values = data.map(d => {
  31. const point = d.data.find(p => p.x === lastTime);
  32. return d.label + ': ' + format(kind, point ? point.y : null);
  33. });
  34. if (kind === 'memory') {
  35. const percentage = metrics.memoryPercent.find(p => p.x === lastTime);
  36. values.push('Uso: ' + (percentage && percentage.y !== null ? percentage.y.toFixed(1) + ' %' : 'Sin datos'));
  37. }
  38. return values.join(' · ') + ' · ' + new Date(lastTime).toLocaleString('es-ES');
  39. }
  40. function start(ChartClass) {
  41. document.querySelectorAll('.vpsmanager-metrics[data-metrics]').forEach(root => {
  42. if (root.dataset.initialized || !root.dataset.metrics) return;
  43. root.dataset.initialized = 'true';
  44. let metrics;
  45. try { metrics = JSON.parse(root.dataset.metrics); } catch (_) { return; }
  46. root.querySelectorAll('[data-chart]').forEach(card => {
  47. const kind = card.dataset.chart;
  48. const data = datasets(kind, metrics[kind]);
  49. const text = card.querySelector('[data-summary]');
  50. const canvas = card.querySelector('canvas');
  51. if (!ChartClass || !data.some(d => d.data.some(p => p.y !== null))) {
  52. text.textContent = unavailable;
  53. canvas.parentElement.hidden = true;
  54. return;
  55. }
  56. text.textContent = summary(kind, data, metrics);
  57. try {
  58. new ChartClass(canvas, {
  59. type: 'line', data: { datasets: data },
  60. options: {
  61. responsive: true, maintainAspectRatio: false, animation: false, parsing: false,
  62. interaction: { mode: 'nearest', intersect: false },
  63. scales: {
  64. 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' }) } },
  65. y: { beginAtZero: true, ticks: { callback: v => format(kind, v) } }
  66. },
  67. plugins: { tooltip: { callbacks: {
  68. title: items => items.length ? new Date(items[0].parsed.x).toLocaleString('es-ES') : '',
  69. label: ctx => ctx.dataset.label + ': ' + format(kind, ctx.parsed.y)
  70. } } }
  71. }
  72. });
  73. } catch (_) {
  74. text.textContent = unavailable;
  75. canvas.parentElement.hidden = true;
  76. }
  77. });
  78. });
  79. }
  80. function boot() {
  81. // Reuse an installed compatible Chart.js; otherwise use the local isolated build.
  82. if (window.Chart && /^4\./.test(window.Chart.version || '')) { start(window.Chart); return; }
  83. if (window.O6HVpsChart) { start(window.O6HVpsChart); return; }
  84. const script = document.createElement('script');
  85. script.src = asset;
  86. script.onload = () => start(window.O6HVpsChart);
  87. script.onerror = () => start(null);
  88. document.head.appendChild(script);
  89. }
  90. if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true });
  91. else boot();
  92. }());