Add display_metrics setting per server

- Add display_metrics JSON field to servers table
- Update server edit form with metric checkboxes
- ServerDetailController now uses server's display_metrics setting
- If no metrics selected - show all (backward compatibility)
- Removed hardcoded metric names from queries
This commit is contained in:
mirivlad 2026-04-25 19:54:00 +08:00
parent a11cfb5165
commit e20354e89d
3 changed files with 73 additions and 13 deletions

View File

@ -141,6 +141,17 @@ class ServerController extends Model
$tokenRow = $stmt->fetch(); $tokenRow = $stmt->fetch();
$decryptedToken = $tokenRow ? \App\Utils\EncryptionHelper::decrypt($tokenRow['encrypted_token']) : null; $decryptedToken = $tokenRow ? \App\Utils\EncryptionHelper::decrypt($tokenRow['encrypted_token']) : null;
// Получаем все метрики которые есть у серве<D0B2><D0B5>а
$stmt = $this->pdo->prepare("
SELECT DISTINCT mn.id, mn.name, mn.unit
FROM metric_names mn
JOIN server_metrics sm ON sm.metric_name_id = mn.id
WHERE sm.server_id = :server_id
ORDER BY mn.name
");
$stmt->execute([':server_id' => $id]);
$allMetrics = $stmt->fetchAll();
if (!$server) { if (!$server) {
return $response->withHeader('Location', '/servers')->withStatus(302); return $response->withHeader('Location', '/servers')->withStatus(302);
} }
@ -149,7 +160,8 @@ class ServerController extends Model
'title' => 'Редактировать сервер', 'title' => 'Редактировать сервер',
'server' => $server, 'server' => $server,
'groups' => $groups, 'groups' => $groups,
'agent_token' => $decryptedToken 'agent_token' => $decryptedToken,
'allMetrics' => $allMetrics
]; ];
return $this->twig->render($response, 'servers/edit.twig', $templateData); return $this->twig->render($response, 'servers/edit.twig', $templateData);
@ -160,6 +172,10 @@ class ServerController extends Model
$id = $args['id']; $id = $args['id'];
$params = $request->getParsedBody(); $params = $request->getParsedBody();
// Собираем выбранные метрики
$displayMetrics = $params['display_metrics'] ?? [];
$displayMetricsJson = json_encode(array_values($displayMetrics));
$stmt = $this->pdo->prepare(" $stmt = $this->pdo->prepare("
UPDATE servers UPDATE servers
SET name = :name, SET name = :name,
@ -167,7 +183,8 @@ class ServerController extends Model
group_id = :group_id, group_id = :group_id,
description = :description, description = :description,
offline_timeout = :offline_timeout, offline_timeout = :offline_timeout,
notify_on_offline = :notify_on_offline notify_on_offline = :notify_on_offline,
display_metrics = :display_metrics
WHERE id = :id WHERE id = :id
"); ");
@ -178,7 +195,8 @@ class ServerController extends Model
':group_id' => $params['group_id'] ?? null, ':group_id' => $params['group_id'] ?? null,
':description' => $params['description'] ?? '', ':description' => $params['description'] ?? '',
':offline_timeout' => (int)($params['offline_timeout'] ?? 300), ':offline_timeout' => (int)($params['offline_timeout'] ?? 300),
':notify_on_offline' => isset($params['notify_on_offline']) ? 1 : 0 ':notify_on_offline' => isset($params['notify_on_offline']) ? 1 : 0,
':display_metrics' => $displayMetricsJson
]); ]);
if ($result) { if ($result) {

View File

@ -38,6 +38,16 @@ class ServerDetailController extends Model
return $response->withHeader('Location', '/servers')->withStatus(302); return $response->withHeader('Location', '/servers')->withStatus(302);
} }
// Получаем настройки отображаемых метрик
$displayMetricsSetting = $server['display_metrics'] ?? null;
$displayMetrics = null;
if ($displayMetricsSetting) {
$decoded = json_decode($displayMetricsSetting, true);
if (is_array($decoded) && count($decoded) > 0) {
$displayMetrics = $decoded;
}
}
// Получаем параметры // Получаем параметры
$queryParams = $request->getQueryParams(); $queryParams = $request->getQueryParams();
$startDate = $queryParams['start'] ?? null; $startDate = $queryParams['start'] ?? null;
@ -121,6 +131,19 @@ class ServerDetailController extends Model
$startStr = $startDate->format('Y-m-d H:i:s'); $startStr = $startDate->format('Y-m-d H:i:s');
$endStr = $endDate->format('Y-m-d H:i:s'); $endStr = $endDate->format('Y-m-d H:i:s');
// Формируем фильтр метрик из настроек сервера
$metricsFilter = '';
$metricParams = [];
if ($displayMetrics) {
$placeholders = [];
foreach ($displayMetrics as $i => $m) {
$key = ':metric_' . $i;
$placeholders[] = $key;
$metricParams[$key] = $m;
}
$metricsFilter = 'AND mn.name IN (' . implode(', ', $placeholders) . ')';
}
// Запрос с агрегацией если нужно // Запрос с агрегацией если нужно
if ($groupBy) { if ($groupBy) {
// Оптимизированный запрос с подзапросом и LIMIT для больших периодов // Оптимизированный запрос с подзапросом и LIMIT для больших периодов
@ -140,15 +163,13 @@ class ServerDetailController extends Model
LIMIT 200000 LIMIT 200000
) sm ) sm
INNER JOIN metric_names mn ON mn.id = sm.metric_name_id INNER JOIN metric_names mn ON mn.id = sm.metric_name_id
WHERE mn.name IN ('cpu_load', 'ram_used', 'ram_total_gb', 'disk_used', 'disk_used_root', WHERE 1=1 {$metricsFilter}
'disk_used_home', 'disk_used_boot', 'disk_total_gb_root', 'disk_total_gb_home',
'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc',
'net_in_enp4s0', 'net_out_enp4s0', 'disk_used_mnt_data')
{$groupBy} {$groupBy}
ORDER BY time_bucket ASC ORDER BY time_bucket ASC
"; ";
$stmt = $this->pdo->prepare($sql); $stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $id, ':start_date' => $startStr, ':end_date' => $endStr]); $executeParams = array_merge([':id' => $id, ':start_date' => $startStr, ':end_date' => $endStr], $metricParams);
$stmt->execute($executeParams);
} else { } else {
$sql = " $sql = "
SELECT sm.value, mn.name, mn.unit, sm.created_at SELECT sm.value, mn.name, mn.unit, sm.created_at
@ -158,15 +179,13 @@ class ServerDetailController extends Model
WHERE sm.server_id = :id WHERE sm.server_id = :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 IN ('cpu_load', 'ram_used', 'ram_total_gb', 'disk_used', 'disk_used_root', {$metricsFilter}
'disk_used_home', 'disk_used_boot', 'disk_total_gb_root', 'disk_total_gb_home',
'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc',
'net_in_enp4s0', 'net_out_enp4s0', 'disk_used_mnt_data')
ORDER BY sm.created_at ASC ORDER BY sm.created_at ASC
LIMIT 5000 LIMIT 5000
"; ";
$stmt = $this->pdo->prepare($sql); $stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $id, ':start_date' => $startStr, ':end_date' => $endStr]); $executeParams = array_merge([':id' => $id, ':start_date' => $startStr, ':end_date' => $endStr], $metricParams);
$stmt->execute($executeParams);
} }
$metrics = $stmt->fetchAll(); $metrics = $stmt->fetchAll();

View File

@ -39,6 +39,29 @@
<small class="form-text text-muted">Дополнительная информация о сервере</small> <small class="form-text text-muted">Дополнительная информация о сервере</small>
</div> </div>
{% if allMetrics|length > 0 %}
<hr>
<h5 class="mb-3"><i class="fas fa-chart-line"></i> Отображаемые метрики</h5>
<p class="text-muted small">Выберите метрики, которые будут показываться на графиках. Пустой выбор = все метрики.</p>
<div class="row mb-3">
{% set savedMetrics = server.display_metrics|json_decode ?: [] %}
{% for metric in allMetrics %}
<div class="col-md-4 col-lg-3 mb-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="display_metrics[]"
id="metric_{{ metric.name }}" value="{{ metric.name }}"
{% if metric.name in savedMetrics %}checked{% endif %}>
<label class="form-check-label" for="metric_{{ metric.name }}">
{{ metric.name }}
{% if metric.unit %}({{ metric.unit }}){% endif %}
</label>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<hr> <hr>
<h5 class="mb-3"><i class="fas fa-wifi"></i> Настройки мониторинга недоступности</h5> <h5 class="mb-3"><i class="fas fa-wifi"></i> Настройки мониторинга недоступности</h5>