diff --git a/templates/servers/detail.twig b/templates/servers/detail.twig
index 12c71b3..3a5a712 100755
--- a/templates/servers/detail.twig
+++ b/templates/servers/detail.twig
@@ -123,7 +123,7 @@
{% for metricName, metricData in metrics %}
- {% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" %}
+ {% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" and not (metricName starts with "temp_") %}
{% endif %}
-
- {% set has_temps = false %}
- {% for m in metrics %}{% if m starts with 'temp_' %}{% set has_temps = true %}{% endif %}{% endfor %}
- {% if has_temps %}
+
+ {% set net_interfaces = [] %}
+ {% for metricName in metrics|keys %}
+ {% if metricName starts with 'net_in_' %}
+ {% set iface = metricName|replace({'net_in_': ''}) %}
+ {% set net_interfaces = net_interfaces|merge([iface]) %}
+ {% endif %}
+ {% endfor %}
+
+ {% for iface in net_interfaces %}
+ {% if metrics['net_in_' ~ iface] is defined and metrics['net_out_' ~ iface] is defined %}
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
@@ -171,12 +197,11 @@
Температуры
-
+
- {% endif %}
@@ -552,7 +577,7 @@ var diskTotalGB = {
// Графики метрик
{% for metricName, metricData in metrics %}
-{% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" %}
+{% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" and not (metricName starts with "temp_") %}
const ctx{{ metricName|replace({'-': '_', '.': '_'}) }} = document.getElementById('chart-{{ metricName }}').getContext('2d');
// Подготовка данных для графика
@@ -726,7 +751,7 @@ chart{{ metricName|replace({'-': '_', '.': '_'}) }}.canvas.addEventListener('mou
// Глобальный обработчик для скрытия тултипов при уходе курсора за пределы canvas
document.addEventListener('mousemove', function(e) {
{% for metricName, metricData in metrics %}
-{% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" %}
+{% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" and not (metricName starts with "temp_") %}
(function() {
var canvas = chart{{ metricName|replace({'-': '_', '.': '_'}) }}.canvas;
var rect = canvas.getBoundingClientRect();
@@ -748,40 +773,61 @@ document.addEventListener('mousemove', function(e) {
-// График температур
-{% set temp_metrics = [] %}
-{% for m in metrics %}{% if m starts with 'temp_' %}{% set temp_metrics = temp_metrics|merge([m]) %}{% endif %}{% endfor %}
-{% if temp_metrics %}
+// Графики сетевых интерфейсов (две линии: In зелёная, Out красная)
+{% set net_interfaces = [] %}
+{% for metricName in metrics|keys %}
+ {% if metricName starts with 'net_in_' %}
+ {% set net_interfaces = net_interfaces|merge([metricName|replace({'net_in_': ''})]) %}
+ {% endif %}
+{% endfor %}
+
+{% for iface in net_interfaces %}
+{% if metrics['net_in_' ~ iface] is defined and metrics['net_out_' ~ iface] is defined %}
(function() {
- var ctx = document.getElementById('chart-temperatures');
+ var ctx = document.getElementById('chart-net-{{ iface }}');
if (!ctx) return;
var labels = [];
- {% if metrics[temp_metrics[0]] is defined %}
- {% for p in metrics[temp_metrics[0]]|slice(-100) %}
- labels.push('{{ p.time_bucket|default(p.created_at)|date("d.m H:i") }}');
- {% endfor %}
- {% endif %}
+ var inData = [];
+ var outData = [];
- var datasets = [];
- {% for m in temp_metrics %}
- var data_{{ m }} = {
- label: '{{ m|replace({'temp_': '', '_': ' '})|title }}',
- data: [],
- fill: false,
- tension: 0.1,
- pointRadius: 1,
- borderWidth: 1
- };
- {% for p in metrics[m]|slice(-100) %}
- data_{{ m }}.data.push({{ p.value }});
- {% endfor %}
- datasets.push(data_{{ m }});
- {% endfor %}
+{% for m in metrics['net_in_' ~ iface]|slice(0, 500)|reverse %}
+ labels.push('{{ m.created_at|date("d.m H:i") }}');
+ inData.push({{ m.value }});
+{% endfor %}
+
+{% set outMetrics = metrics['net_out_' ~ iface]|slice(0, 500)|reverse %}
+{% for m in outMetrics %}
+ outData.push({{ m.value }});
+{% endfor %}
new Chart(ctx.getContext('2d'), {
type: 'line',
- data: { labels: labels, datasets: datasets },
+ data: {
+ labels: labels,
+ datasets: [
+ {
+ label: 'Входящий (In)',
+ data: inData,
+ borderColor: 'rgba(25, 135, 84, 1)',
+ backgroundColor: 'rgba(25, 135, 84, 0.1)',
+ fill: true,
+ tension: 0.1,
+ pointRadius: 0,
+ borderWidth: 1.5
+ },
+ {
+ label: 'Исходящий (Out)',
+ data: outData,
+ borderColor: 'rgba(220, 53, 69, 1)',
+ backgroundColor: 'rgba(220, 53, 69, 0.1)',
+ fill: true,
+ tension: 0.1,
+ pointRadius: 0,
+ borderWidth: 1.5
+ }
+ ]
+ },
options: {
responsive: true,
maintainAspectRatio: false,
@@ -789,13 +835,97 @@ document.addEventListener('mousemove', function(e) {
plugins: {
legend: { display: true, position: 'top' },
tooltip: { enabled: true, mode: 'index', intersect: false },
- zoom: { zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x' }, pan: { enabled: true, mode: 'x' } }
+ zoom: {
+ zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x' },
+ pan: { enabled: true, mode: 'x' }
+ }
},
- scales: { y: { beginAtZero: false, ticks: { callback: v => v + '°C' } } }
+ scales: {
+ y: { beginAtZero: true, ticks: { callback: function(v) { return v + '%'; } } }
+ }
}
});
})();
{% endif %}
+{% endfor %}
+
+
+// График температур — ВСЕ temp_* на одном графике
+(function() {
+ var ctx = document.getElementById('chart-temperatures');
+ if (!ctx) return;
+
+ var tempColors = [
+ { border: 'rgba(255, 99, 132, 1)', bg: 'rgba(255, 99, 132, 0.1)' },
+ { border: 'rgba(75, 192, 192, 1)', bg: 'rgba(75, 192, 192, 0.1)' },
+ { border: 'rgba(255, 159, 64, 1)', bg: 'rgba(255, 159, 64, 0.1)' },
+ { border: 'rgba(255, 205, 86, 1)', bg: 'rgba(255, 205, 86, 0.1)' },
+ { border: 'rgba(201, 203, 207, 1)', bg: 'rgba(201, 203, 207, 0.1)' },
+ { border: 'rgba(54, 162, 235, 1)', bg: 'rgba(54, 162, 235, 0.1)' },
+ { border: 'rgba(153, 102, 255, 1)', bg: 'rgba(153, 102, 255, 0.1)' },
+ { border: 'rgba(255, 99, 255, 1)', bg: 'rgba(255, 99, 255, 0.1)' },
+ { border: 'rgba(100, 200, 100, 1)', bg: 'rgba(100, 200, 100, 0.1)' },
+ { border: 'rgba(200, 150, 50, 1)', bg: 'rgba(200, 150, 50, 0.1)' }
+ ];
+
+ var labels = [];
+ var labelsFilled = false;
+ var datasets = [];
+ var colorIdx = 0;
+
+ {% for mn, md in metrics %}
+ {% if mn starts with 'temp_' %}
+ if (!labelsFilled) {
+ {% for p in md|slice(-1000) %}
+ labels.push('{{ p.time_bucket|default(p.created_at)|date("d.m H:i") }}');
+ {% endfor %}
+ labelsFilled = true;
+ }
+
+ (function() {
+ var data = [];
+ {% for p in md|slice(-1000) %}
+ data.push({{ p.value }});
+ {% endfor %}
+
+ var color = tempColors[colorIdx % tempColors.length];
+ colorIdx++;
+
+ datasets.push({
+ label: '{{ mn|replace({'temp_': '', '_': ' '})|title }}',
+ data: data,
+ borderColor: color.border,
+ backgroundColor: color.bg,
+ fill: false,
+ tension: 0.1,
+ pointRadius: 1,
+ borderWidth: 2
+ });
+ })();
+ {% endif %}
+ {% endfor %}
+
+ var chart = new Chart(ctx.getContext('2d'), {
+ type: 'line',
+ data: { labels: labels, datasets: datasets },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ interaction: { mode: 'index', intersect: false },
+ plugins: {
+ legend: { display: true, position: 'top', labels: { boxWidth: 12, font: { size: 11 } } },
+ tooltip: { enabled: true, mode: 'index', intersect: false },
+ zoom: {
+ zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x' },
+ pan: { enabled: true, mode: 'x' }
+ }
+ },
+ scales: { y: { beginAtZero: false, ticks: { callback: function(v) { return v + '°C'; } } } }
+ }
+ });
+
+ window.chartTemperatures = chart;
+})();
// Doughnut графики для разделов дисков
{% for metricName, metricData in metrics %}
@@ -842,12 +972,15 @@ document.addEventListener('mousemove', function(e) {
// Сбросить зум на всех графиках
function resetAllZoom() {
{% for metricName, metricData in metrics %}
-{% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" %}
+{% if metricName!="top_cpu_proc" and metricName!="top_ram_proc" and metricName!="disk_used" and not (metricName starts with "disk_used_") and not (metricName starts with "disk_total_gb_") and metricName!="ram_total_gb" and not (metricName starts with "net_in_") and not (metricName starts with "net_out_") and metricName!="network_rx" and metricName!="network_tx" and not (metricName starts with "temp_") %}
if (typeof chart{{ metricName|replace({'-': '_', '.': '_'}) }} !== 'undefined') {
chart{{ metricName|replace({'-': '_', '.': '_'}) }}.resetZoom();
}
{% endif %}
{% endfor %}
+ if (typeof window.chartTemperatures !== 'undefined') {
+ window.chartTemperatures.resetZoom();
+ }
}