Fix metric display: remove fallback, add displayMetrics check

- Remove hardcoded fallback metrics in visibleMetrics
- Add message 'Choose metrics in server settings' if displayMetrics is empty
- Update disk/temp/network sections to use displayMetrics
- Add grouped metrics (diskMetrics, tempMetrics, netInMetrics, netOutMetrics)
- Only display metrics that user selected in server settings
This commit is contained in:
mirivlad 2026-04-26 15:19:36 +08:00
parent 6d301e7273
commit 023c441e66
4 changed files with 44 additions and 148 deletions

View File

@ -16,7 +16,7 @@ $pdo = new PDO("mysql:host={$config['host']};dbname={$config['db_name']};charset
// Вычисляем период - прошлый час (00:00 - 00:59) // Вычисляем период - прошлый час (00:00 - 00:59)
$hourStart = new DateTime(); $hourStart = new DateTime();
$hourStart->modify('-1 hour'); $hourStart->modify('-1 hour');
$hourStart->setMinute(0)->setSecond(0); $hourStart->setTime((int)$hourStart->format('H'), 0, 0);
$hourEnd = clone $hourStart; $hourEnd = clone $hourStart;
$hourEnd->modify('+59 minutes +59 seconds'); $hourEnd->modify('+59 minutes +59 seconds');
@ -25,7 +25,7 @@ $periodStartStr = $hourStart->format('Y-m-d H:i:s');
$periodEndStr = $hourEnd->format('Y-m-d H:i:s'); $periodEndStr = $hourEnd->format('Y-m-d H:i:s');
// Получаем все серверы // Получаем все серверы
$stmt = $pdo->query("SELECT id FROM servers WHERE deleted_at IS NULL"); $stmt = $pdo->query("SELECT id FROM servers");
$servers = $stmt->fetchAll(PDO::FETCH_COLUMN); $servers = $stmt->fetchAll(PDO::FETCH_COLUMN);
$processed = 0; $processed = 0;
@ -39,14 +39,16 @@ foreach ($servers as $serverId) {
sm.server_id, sm.server_id,
sm.metric_name_id, sm.metric_name_id,
:period_start, :period_start,
AVG(sm.value), AVG(CAST(sm.value AS DECIMAL(20,4))),
MIN(sm.value), MIN(CAST(sm.value AS DECIMAL(20,4))),
MAX(sm.value), MAX(CAST(sm.value AS DECIMAL(20,4))),
COUNT(*) COUNT(*)
FROM server_metrics sm FROM server_metrics sm
INNER JOIN metric_names mn ON sm.metric_name_id = mn.id
WHERE sm.server_id = :server_id WHERE sm.server_id = :server_id
AND sm.created_at >= :start_date AND sm.created_at >= :start_date
AND sm.created_at <= :end_date AND sm.created_at <= :end_date
AND mn.name NOT IN ('top_cpu_proc', 'top_ram_proc')
GROUP BY sm.server_id, sm.metric_name_id GROUP BY sm.server_id, sm.metric_name_id
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
avg_value = VALUES(avg_value), avg_value = VALUES(avg_value),

View File

@ -15,7 +15,7 @@ $errors = 0;
for ($i = 1; $i <= $hours; $i++) { for ($i = 1; $i <= $hours; $i++) {
$hourStart = new DateTime(); $hourStart = new DateTime();
$hourStart->modify("-{$i} hour"); $hourStart->modify("-{$i} hour");
$hourStart->setMinute(0)->setSecond(0); $hourStart->setTime((int)$hourStart->format('H'), 0, 0);
$hourEnd = clone $hourStart; $hourEnd = clone $hourStart;
$hourEnd->modify('+59 minutes +59 seconds'); $hourEnd->modify('+59 minutes +59 seconds');
@ -24,24 +24,27 @@ for ($i = 1; $i <= $hours; $i++) {
$periodEndStr = $hourEnd->format('Y-m-d H:59:59'); $periodEndStr = $hourEnd->format('Y-m-d H:59:59');
// Получаем все серверы // Получаем все серверы
$stmt = $pdo->query("SELECT id FROM servers WHERE deleted_at IS NULL"); $stmt = $pdo->query("SELECT id FROM servers");
$servers = $stmt->fetchAll(PDO::FETCH_COLUMN); $servers = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($servers as $serverId) { foreach ($servers as $serverId) {
// Исключаем метрики с JSON данными (top_cpu_proc, top_ram_proc)
$sql = " $sql = "
INSERT INTO server_metrics_trends (server_id, metric_name_id, period_start, avg_value, min_value, max_value, count_samples) INSERT INTO server_metrics_trends (server_id, metric_name_id, period_start, avg_value, min_value, max_value, count_samples)
SELECT SELECT
sm.server_id, sm.server_id,
sm.metric_name_id, sm.metric_name_id,
:period_start, :period_start,
AVG(sm.value), AVG(CAST(sm.value AS DECIMAL(20,4))),
MIN(sm.value), MIN(CAST(sm.value AS DECIMAL(20,4))),
MAX(sm.value), MAX(CAST(sm.value AS DECIMAL(20,4))),
COUNT(*) COUNT(*)
FROM server_metrics sm FROM server_metrics sm
INNER JOIN metric_names mn ON sm.metric_name_id = mn.id
WHERE sm.server_id = :server_id WHERE sm.server_id = :server_id
AND sm.created_at >= :start_date AND sm.created_at >= :start_date
AND sm.created_at <= :end_date AND sm.created_at <= :end_date
AND mn.name NOT IN ('top_cpu_proc', 'top_ram_proc')
GROUP BY sm.server_id, sm.metric_name_id GROUP BY sm.server_id, sm.metric_name_id
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
avg_value = VALUES(avg_value), avg_value = VALUES(avg_value),

View File

@ -333,6 +333,19 @@ class ServerDetailController extends Model
'server' => $server, 'server' => $server,
'metrics' => $groupedMetrics, 'metrics' => $groupedMetrics,
'displayMetrics' => $displayMetrics, 'displayMetrics' => $displayMetrics,
// Группировка метрик по категориям для отдельных секций
'diskMetrics' => array_filter($groupedMetrics, function($key) {
return str_starts_with($key, 'disk_used_');
}, ARRAY_FILTER_USE_KEY),
'tempMetrics' => array_filter($groupedMetrics, function($key) {
return str_starts_with($key, 'temp_');
}, ARRAY_FILTER_USE_KEY),
'netInMetrics' => array_filter($groupedMetrics, function($key) {
return str_starts_with($key, 'net_in_');
}, ARRAY_FILTER_USE_KEY),
'netOutMetrics' => array_filter($groupedMetrics, function($key) {
return str_starts_with($key, 'net_out_');
}, ARRAY_FILTER_USE_KEY),
'allMetricTypes' => $allMetricTypes, 'allMetricTypes' => $allMetricTypes,
'existingThresholds' => $existingThresholds, 'existingThresholds' => $existingThresholds,
'allServices' => $allServices, 'allServices' => $allServices,

View File

@ -23,137 +23,15 @@
</a> </a>
</div> </div>
</div> </div>
<div class="card-body">
<!-- Информация о сервере и аптайм --> {% if not displayMetrics or displayMetrics is empty %}
<div class="row mb-4"> <div class="alert alert-warning">
<div class="col-md-8"> <i class="fas fa-info-circle"></i>
<h5>Информация о сервере</h5> Выберите метрики для отображения в <a href="/servers/{{ server.id }}/edit">настройках сервера</a>
<table class="table table-borderless"> </div>
<tr>
<td><strong>Название:</strong></td>
<td>{{ server.name }}</td>
</tr>
<tr>
<td><strong>Адрес:</strong></td>
<td>{{ server.address|default('-') }}</td>
</tr>
<tr>
<td><strong>Группа:</strong></td>
<td>
{% if server.group_name %}
<i class="fas {{ server.group_icon|default('fa-box') }}" {% if server.group_color %}style="color: {{ server.group_color }}"{% endif %}></i> {{ server.group_name }}
{% else %} {% else %}
-
{% endif %}
</td>
</tr>
<tr>
<td><strong>Описание:</strong></td>
<td>{{ server.description|default('-') }}</td>
</tr>
<tr>
<td><strong>Последние метрики:</strong></td>
<td>
{% if server.last_metrics_at %}
{{ server.last_metrics_at|date('d.m.Y H:i:s') }}
{% else %}
Нет данных
{% endif %}
</td>
</tr>
</table>
</div>
<div class="col-md-4">
<div class="card border-primary h-100">
<div class="card-header bg-primary text-white">
<h6 class="mb-0"><i class="fas fa-clock"></i> Время работы</h6>
</div>
<div class="card-body text-center d-flex flex-column justify-content-center">
{% if latestUptime is defined %}
{% set uptime_sec = latestUptime.value %}
<div class="mb-2">
{% if uptime_sec >= 86400 %}
<span class="badge bg-success fs-6 me-1">{{ (uptime_sec / 86400)|round(0, 'floor') }}д</span>
{% endif %}
{% if uptime_sec >= 3600 %}
<span class="badge bg-info fs-6 me-1">{{ ((uptime_sec % 86400) / 3600)|round(0, 'floor') }}ч</span>
{% endif %}
{% if uptime_sec >= 60 %}
<span class="badge bg-secondary fs-6">{{ ((uptime_sec % 3600) / 60)|round(0, 'floor') }}м</span>
{% endif %}
</div>
<small class="text-muted">
<i class="fas fa-play"></i> {{ server.last_seen|date('d.m.Y H:i') }}
</small>
{% else %}
<div class="text-muted">
<i class="fas fa-clock fa-2x mb-2"></i>
<p class="mb-0">Нет данных</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Вкладки -->
<ul class="nav nav-tabs" id="serverTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active" id="metrics-tab" data-bs-toggle="tab" data-bs-target="#metrics" type="button" role="tab">
<i class="fas fa-chart-line"></i> Метрики
</button>
</li>
<li class="nav-item">
<button class="nav-link" id="services-tab" data-bs-toggle="tab" data-bs-target="#services" type="button" role="tab">
<i class="fas fa-cogs"></i> Сервисы
</button>
</li>
<li class="nav-item">
<button class="nav-link" id="thresholds-tab" data-bs-toggle="tab" data-bs-target="#thresholds" type="button" role="tab">
<i class="fas fa-bell"></i> Пороги
</button>
</li>
</ul>
<!-- Содержимое вкладок -->
<div class="tab-content mt-3">
<!-- Вкладка "Метрики" -->
<div class="tab-pane fade show active" id="metrics" role="tabpanel">
<div class="row mt-2 mb-2">
<div class="col-md-12">
<small class="text-muted">Период:</small>
</div>
<div class="col-md-12 mt-1">
<div class="btn-group d-flex" role="group">
<a href="?tab=metrics&amp;period=1h" class="btn btn-outline-primary w-100 {% if period == '1h' %}active{% endif %}">
</a>
<a href="?tab=metrics&amp;period=6h" class="btn btn-outline-primary w-100 {% if period == '6h' or period is empty %}active{% endif %}">
</a>
<a href="?tab=metrics&amp;period=24h" class="btn btn-outline-primary w-100 {% if period == '24h' %}active{% endif %}">
24ч
</a>
<a href="?tab=metrics&amp;period=7d" class="btn btn-outline-primary w-100 {% if period == '7d' %}active{% endif %}">
</a>
<a href="?tab=metrics&amp;period=30d" class="btn btn-outline-primary w-100 {% if period == '30d' %}active{% endif %}">
30д
</a>
</div>
<div class="btn-group d-flex mt-1" role="group">
<button type="button" class="btn btn-sm btn-outline-secondary w-100" onclick="resetAllZoom()" title="Сбросить интерактивный зум">
<i class="fas fa-search-minus"></i> Сбросить зум
</button>
</div>
<small class="text-muted">💡 Колёсико мыши = зум, перетаскивание = выделение области, Shift+колёсико = панорама</small>
</div>
</div>
<div class="row"> <div class="row">
{% set visibleMetrics = displayMetrics ?: ['cpu_load', 'ram_used', 'disk_used_root', 'disk_used_home', 'disk_used_boot', 'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc'] %} {% set visibleMetrics = displayMetrics %}
{% for metricName, metricData in metrics %} {% for metricName, metricData in metrics %}
{% if metricName in visibleMetrics %} {% if metricName in visibleMetrics %}
<div class="col-12 mb-4"> <div class="col-12 mb-4">
@ -194,7 +72,7 @@
</div> </div>
<!-- Графики сетевых интерфейсов --> <!-- Графики сетевых интерфейсов -->
{% set net_interfaces = [] %} {% set net_interfaces = [] %}
{% for metricName in metrics|keys %} {% for metricName in displayMetrics %}
{% if metricName starts with 'net_in_' %} {% if metricName starts with 'net_in_' %}
{% set iface = metricName|replace({'net_in_': ''}) %} {% set iface = metricName|replace({'net_in_': ''}) %}
{% set net_interfaces = net_interfaces|merge([iface]) %} {% set net_interfaces = net_interfaces|merge([iface]) %}
@ -203,7 +81,7 @@
{% if net_interfaces|length > 0 %} {% if net_interfaces|length > 0 %}
{% for iface in net_interfaces %} {% for iface in net_interfaces %}
{% if metrics['net_in_' ~ iface] is defined and metrics['net_out_' ~ iface] is defined %} {% if iface in displayMetrics and ('net_in_' ~ iface) in displayMetrics and ('net_out_' ~ iface) in displayMetrics and metrics['net_in_' ~ iface] is defined and metrics['net_out_' ~ iface] is defined %}
<div class="row"> <div class="row">
<div class="col-12 mb-4"> <div class="col-12 mb-4">
<div class="card"> <div class="card">
@ -229,7 +107,7 @@
<!-- Температуры: один общий график --> <!-- Температуры: один общий график -->
{% set has_temps = false %} {% set has_temps = false %}
{% for metricName in metrics|keys %} {% for metricName in displayMetrics %}
{% if metricName starts with 'temp_' %} {% if metricName starts with 'temp_' %}
{% set has_temps = true %} {% set has_temps = true %}
{% endif %} {% endif %}
@ -256,7 +134,7 @@
<!-- Диски: Doughnut графики --> <!-- Диски: Doughnut графики -->
{% set has_disk_parts = false %} {% set has_disk_parts = false %}
{% for metricName in metrics|keys %} {% for metricName in displayMetrics %}
{% if metricName starts with 'disk_used_' and metricName != 'disk_used' %} {% if metricName starts with 'disk_used_' and metricName != 'disk_used' %}
{% set has_disk_parts = true %} {% set has_disk_parts = true %}
{% endif %} {% endif %}
@ -265,7 +143,7 @@
<div class="row mb-3"> <div class="row mb-3">
{% if has_disk_parts %} {% if has_disk_parts %}
{% for metricName, metricData in metrics %} {% for metricName, metricData in metrics %}
{% if metricName starts with 'disk_used_' and metricName != 'disk_used' %} {% if metricName starts with 'disk_used_' and metricName != 'disk_used' and metricName in displayMetrics %}
<div class="col-md-4 mb-3"> <div class="col-md-4 mb-3">
<div class="card h-100"> <div class="card h-100">
<div class="card-body text-center"> <div class="card-body text-center">
@ -667,7 +545,7 @@ var diskTotalGB = {
}; };
// Графики метрик // Графики метрик
{% set visibleMetrics = displayMetrics ?: ['cpu_load', 'ram_used', 'disk_used_root', 'disk_used_home', 'disk_used_boot', 'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc'] %} {% set visibleMetrics = displayMetrics %}
{% for metricName, metricData in metrics %} {% for metricName, metricData in metrics %}
{% if metricName in visibleMetrics and metricName != 'uptime' %} {% if metricName in visibleMetrics and metricName != 'uptime' %}
const ctx{{ metricName|replace({'-': '_', '.': '_'}) }} = document.getElementById('chart-{{ metricName }}').getContext('2d'); const ctx{{ metricName|replace({'-': '_', '.': '_'}) }} = document.getElementById('chart-{{ metricName }}').getContext('2d');
@ -895,7 +773,7 @@ chart{{ metricName|replace({'-': '_', '.': '_'}) }}.canvas.addEventListener('mou
// Глобальный обработчик mousemove для скрытия тултипов при уходе курсора за пределы canvas // Глобальный обработчик mousemove для скрытия тултипов при уходе курсора за пределы canvas
// Глобальный обработчик для скрытия тултипов при уходе курсора за пределы canvas // Глобальный обработчик для скрытия тултипов при уходе курсора за пределы canvas
document.addEventListener('mousemove', function(e) { document.addEventListener('mousemove', function(e) {
{% set visibleMetrics = displayMetrics ?: ['cpu_load', 'ram_used', 'disk_used_root', 'disk_used_home', 'disk_used_boot', 'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc'] %} {% set visibleMetrics = displayMetrics %}
{% for metricName, metricData in metrics %} {% for metricName, metricData in metrics %}
{% if metricName in visibleMetrics and metricName != 'uptime' %} {% if metricName in visibleMetrics and metricName != 'uptime' %}
(function() { (function() {
@ -1117,7 +995,7 @@ document.addEventListener('mousemove', function(e) {
// Сбросить зум на всех графиках // Сбросить зум на всех графиках
function resetAllZoom() { function resetAllZoom() {
{% set visibleMetrics = displayMetrics ?: ['cpu_load', 'ram_used', 'disk_used_root', 'disk_used_home', 'disk_used_boot', 'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc'] %} {% set visibleMetrics = displayMetrics %}
{% for metricName, metricData in metrics %} {% for metricName, metricData in metrics %}
{% if metricName in visibleMetrics and metricName != 'uptime' %} {% if metricName in visibleMetrics and metricName != 'uptime' %}
if (typeof chart{{ metricName|replace({'-': '_', '.': '_'}) }} !== 'undefined') { if (typeof chart{{ metricName|replace({'-': '_', '.': '_'}) }} !== 'undefined') {