Fix tooltips and uptime chart issues
- Fix tooltip data access: use chart data instead of API response - Exclude uptime from metrics charts (SQL and template filters) - Fix fetchProcesses() time format handling - Add try-catch and proper Response handling in getProcesses API - Fix Slim Response write() chaining issue - Add last_seen to server query
This commit is contained in:
parent
3fecc21565
commit
66d4d021ba
|
|
@ -6,6 +6,7 @@ use App\Controllers\AgentController;
|
||||||
use App\Controllers\AdminController;
|
use App\Controllers\AdminController;
|
||||||
use App\Controllers\AlertController;
|
use App\Controllers\AlertController;
|
||||||
use App\Controllers\Api\MetricsController;
|
use App\Controllers\Api\MetricsController;
|
||||||
|
use App\Controllers\Api\MetricsApiController;
|
||||||
use App\Controllers\GroupController;
|
use App\Controllers\GroupController;
|
||||||
use App\Controllers\ServerController;
|
use App\Controllers\ServerController;
|
||||||
use App\Controllers\ServerDetailController;
|
use App\Controllers\ServerDetailController;
|
||||||
|
|
@ -187,6 +188,10 @@ $agentController = new AgentController();
|
||||||
$dashboardApiController = new DashboardController($twig);
|
$dashboardApiController = new DashboardController($twig);
|
||||||
$app->get('/api/dashboard/stats', [$dashboardApiController, 'getDashboardData'])->add(AuthMiddleware::class);
|
$app->get('/api/dashboard/stats', [$dashboardApiController, 'getDashboardData'])->add(AuthMiddleware::class);
|
||||||
|
|
||||||
|
// API для метрик сервера (динамическая загрузка)
|
||||||
|
$metricsApiController = new MetricsApiController();
|
||||||
|
$app->get('/api/servers/{id}/metrics', [$metricsApiController, 'getServerMetrics'])->add(AuthMiddleware::class);
|
||||||
|
|
||||||
// Routes for groups (protected with auth middleware and csrf)
|
// Routes for groups (protected with auth middleware and csrf)
|
||||||
$groupsGroup = $app->group('/groups', function ($group) use ($groupController) {
|
$groupsGroup = $app->group('/groups', function ($group) use ($groupController) {
|
||||||
$group->get('', [$groupController, 'index']);
|
$group->get('', [$groupController, 'index']);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
<?php
|
||||||
|
// src/Controllers/Api/MetricsApiController.php
|
||||||
|
|
||||||
|
namespace App\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Models\Model;
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class MetricsApiController extends Model
|
||||||
|
{
|
||||||
|
private const MAX_POINTS = 500;
|
||||||
|
|
||||||
|
public function getServerMetrics(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$id = $args['id'];
|
||||||
|
|
||||||
|
// Параметры
|
||||||
|
$queryParams = $request->getQueryParams();
|
||||||
|
$period = $queryParams['period'] ?? '7d';
|
||||||
|
$startParam = $queryParams['start'] ?? null;
|
||||||
|
$endParam = $queryParams['end'] ?? null;
|
||||||
|
$zoom = $queryParams['zoom'] ?? null;
|
||||||
|
|
||||||
|
// Вычисляем даты
|
||||||
|
$endDate = new DateTime();
|
||||||
|
$startDate = clone $endDate;
|
||||||
|
|
||||||
|
if ($startParam && $endParam) {
|
||||||
|
// Используем переданные даты
|
||||||
|
$startDate = new DateTime($startParam);
|
||||||
|
$endDate = new DateTime($endParam);
|
||||||
|
} else {
|
||||||
|
// Вычисляем по period
|
||||||
|
switch ($period) {
|
||||||
|
case '1h':
|
||||||
|
$startDate->modify('-1 hour');
|
||||||
|
break;
|
||||||
|
case '6h':
|
||||||
|
$startDate->modify('-6 hours');
|
||||||
|
break;
|
||||||
|
case '7d':
|
||||||
|
$startDate->modify('-7 days');
|
||||||
|
break;
|
||||||
|
case '30d':
|
||||||
|
$startDate->modify('-30 days');
|
||||||
|
break;
|
||||||
|
case '24h':
|
||||||
|
default:
|
||||||
|
$startDate->modify('-24 hours');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем интервал агрегации
|
||||||
|
$totalMinutes = ($endDate->getTimestamp() - $startDate->getTimestamp()) / 60;
|
||||||
|
$aggregationMinutes = max(1, ceil($totalMinutes / self::MAX_POINTS));
|
||||||
|
$aggregationSeconds = $aggregationMinutes * 60;
|
||||||
|
|
||||||
|
// Получаем метрики для графиков (исключая uptime и top_*)
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
mn.name,
|
||||||
|
mn.unit,
|
||||||
|
FLOOR(TIMESTAMPDIFF(SECOND, :start, sm.created_at) / :agg) as bucket,
|
||||||
|
AVG(sm.value) as value,
|
||||||
|
MAX(sm.created_at) as created_at
|
||||||
|
FROM server_metrics sm
|
||||||
|
JOIN metric_names mn ON sm.metric_name_id = mn.id
|
||||||
|
WHERE sm.server_id = :server_id
|
||||||
|
AND sm.created_at BETWEEN :start AND :end
|
||||||
|
AND mn.name NOT IN ('uptime')
|
||||||
|
AND mn.name NOT LIKE '%_proc'
|
||||||
|
AND (
|
||||||
|
mn.name IN ('cpu_load', 'ram_used')
|
||||||
|
OR mn.name LIKE 'disk_used_%'
|
||||||
|
OR mn.name LIKE 'net_in_%'
|
||||||
|
OR mn.name LIKE 'net_out_%'
|
||||||
|
OR mn.name LIKE 'temp_%'
|
||||||
|
)
|
||||||
|
GROUP BY mn.name, bucket
|
||||||
|
ORDER BY mn.name, bucket
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':server_id' => $id,
|
||||||
|
':start' => $startDate->format('Y-m-d H:i:s'),
|
||||||
|
':end' => $endDate->format('Y-m-d H:i:s'),
|
||||||
|
':agg' => $aggregationSeconds
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rawData = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Группируем по метрикам
|
||||||
|
$datasets = [];
|
||||||
|
$labels = [];
|
||||||
|
$minTime = null;
|
||||||
|
$maxTime = null;
|
||||||
|
|
||||||
|
foreach ($rawData as $row) {
|
||||||
|
$metricName = $row['name'];
|
||||||
|
$time = $row['created_at'];
|
||||||
|
|
||||||
|
if (!isset($datasets[$metricName])) {
|
||||||
|
$datasets[$metricName] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$datasets[$metricName][] = (float)$row['value'];
|
||||||
|
|
||||||
|
// Собираем уникальные метки времени
|
||||||
|
if (!in_array($time, $labels)) {
|
||||||
|
$labels[] = $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($minTime === null || $time < $minTime) $minTime = $time;
|
||||||
|
if ($maxTime === null || $time > $maxTime) $maxTime = $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Форматируем labels
|
||||||
|
$formattedLabels = array_map(function($label) {
|
||||||
|
return (new DateTime($label))->format('d.m H:i');
|
||||||
|
}, $labels);
|
||||||
|
|
||||||
|
// Получаем TOP процессы для последней точки
|
||||||
|
$topCpu = [];
|
||||||
|
$topRam = [];
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT mn.name, sm.value
|
||||||
|
FROM server_metrics sm
|
||||||
|
JOIN metric_names mn ON sm.metric_name_id = mn.id
|
||||||
|
WHERE sm.server_id = :server_id AND mn.name = 'top_cpu_proc'
|
||||||
|
ORDER BY sm.created_at DESC
|
||||||
|
LIMIT 5
|
||||||
|
");
|
||||||
|
$stmt->execute([':server_id' => $id]);
|
||||||
|
$topData = $stmt->fetchAll();
|
||||||
|
if ($topData) {
|
||||||
|
$topCpu = json_decode($topData[0]['value'] ?? '[]', true) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT sm.value
|
||||||
|
FROM server_metrics sm
|
||||||
|
JOIN metric_names mn ON sm.metric_name_id = mn.id
|
||||||
|
WHERE sm.server_id = :server_id AND mn.name = 'top_ram_proc'
|
||||||
|
ORDER BY sm.created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([':server_id' => $id]);
|
||||||
|
$topRamData = $stmt->fetch();
|
||||||
|
if ($topRamData) {
|
||||||
|
$topRam = json_decode($topRamData['value'] ?? '[]', true) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем ответ
|
||||||
|
$result = [
|
||||||
|
'period' => $period,
|
||||||
|
'start' => $startDate->format('Y-m-d H:i:s'),
|
||||||
|
'end' => $endDate->format('Y-m-d H:i:s'),
|
||||||
|
'aggregation_minutes' => $aggregationMinutes,
|
||||||
|
'total_points' => count($labels),
|
||||||
|
'labels' => $formattedLabels,
|
||||||
|
'datasets' => $datasets,
|
||||||
|
'top_cpu' => $topCpu,
|
||||||
|
'top_ram' => $topRam
|
||||||
|
];
|
||||||
|
|
||||||
|
$response->getBody()->write(json_encode($result, JSON_UNESCAPED_UNICODE));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -436,11 +436,13 @@ class MetricsController extends Model
|
||||||
|
|
||||||
public function getProcesses(Request $request, Response $response, $args)
|
public function getProcesses(Request $request, Response $response, $args)
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$serverId = $args['id'];
|
$serverId = $args['id'];
|
||||||
$timeParam = $request->getQueryParams()['time'] ?? null;
|
$timeParam = $request->getQueryParams()['time'] ?? null;
|
||||||
|
|
||||||
if (!$timeParam) {
|
if (!$timeParam) {
|
||||||
return $response->withStatus(400)->getBody()->write(json_encode(['error' => 'Time parameter required']));
|
$response->getBody()->write(json_encode(['error' => 'Time parameter required']));
|
||||||
|
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
||||||
}
|
}
|
||||||
|
|
||||||
$timestamp = strtotime($timeParam);
|
$timestamp = strtotime($timeParam);
|
||||||
|
|
@ -454,8 +456,15 @@ class MetricsController extends Model
|
||||||
$timestamp = strtotime($today . ' ' . $timeParam);
|
$timestamp = strtotime($today . ' ' . $timeParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($timestamp === false) {
|
// Если timestamp всё ещё false или равен 0 (1970) - возвращаем пустой результат
|
||||||
return $response->withStatus(400)->getBody()->write(json_encode(['error' => 'Invalid time format']));
|
if ($timestamp === false || $timestamp < 0) {
|
||||||
|
$response->getBody()->write(json_encode([
|
||||||
|
'top_cpu' => [],
|
||||||
|
'top_ram' => [],
|
||||||
|
'time' => $timeParam,
|
||||||
|
'error' => 'Invalid time format'
|
||||||
|
]));
|
||||||
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
}
|
}
|
||||||
|
|
||||||
$time = date('Y-m-d H:i:s', $timestamp);
|
$time = date('Y-m-d H:i:s', $timestamp);
|
||||||
|
|
@ -490,6 +499,12 @@ class MetricsController extends Model
|
||||||
]));
|
]));
|
||||||
|
|
||||||
return $response->withHeader('Content-Type', 'application/json');
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$response->getBody()->write(json_encode([
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]));
|
||||||
|
return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMetrics(Request $request, Response $response, $args)
|
public function getMetrics(Request $request, Response $response, $args)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ class ServerDetailController extends Model
|
||||||
|
|
||||||
// Получаем информацию о сервере
|
// Получаем информацию о сервере
|
||||||
$stmt = $this->pdo->prepare("
|
$stmt = $this->pdo->prepare("
|
||||||
SELECT s.*, sg.name as group_name, sg.icon as group_icon, sg.color as group_color
|
SELECT s.*, sg.name as group_name, sg.icon as group_icon, sg.color as group_color,
|
||||||
|
(SELECT MAX(sm.created_at) FROM server_metrics sm WHERE sm.server_id = s.id) as last_seen
|
||||||
FROM servers s
|
FROM servers s
|
||||||
LEFT JOIN server_groups sg ON s.group_id = sg.id
|
LEFT JOIN server_groups sg ON s.group_id = sg.id
|
||||||
WHERE s.id = :id
|
WHERE s.id = :id
|
||||||
|
|
@ -133,6 +134,7 @@ 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 != 'uptime'
|
||||||
{$groupBy}
|
{$groupBy}
|
||||||
ORDER BY time_bucket ASC
|
ORDER BY time_bucket ASC
|
||||||
";
|
";
|
||||||
|
|
@ -146,6 +148,7 @@ 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 != 'uptime'
|
||||||
ORDER BY sm.created_at ASC
|
ORDER BY sm.created_at ASC
|
||||||
";
|
";
|
||||||
$stmt = $this->pdo->prepare($sql);
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
|
@ -246,6 +249,19 @@ class ServerDetailController extends Model
|
||||||
$monitorServices = json_decode($agentConfig['monitor_services'], true) ?? [];
|
$monitorServices = json_decode($agentConfig['monitor_services'], true) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Получаем последние значения метрик (для виджета аптайма)
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
SELECT mn.name, sm.value, sm.created_at
|
||||||
|
FROM server_metrics sm
|
||||||
|
JOIN metric_names mn ON sm.metric_name_id = mn.id
|
||||||
|
WHERE sm.server_id = :id
|
||||||
|
AND mn.name = 'uptime'
|
||||||
|
ORDER BY sm.created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
$latestUptime = $stmt->fetch();
|
||||||
|
|
||||||
$templateData = [
|
$templateData = [
|
||||||
'title' => 'Сервер: ' . $server['name'],
|
'title' => 'Сервер: ' . $server['name'],
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
|
|
@ -254,6 +270,7 @@ class ServerDetailController extends Model
|
||||||
'existingThresholds' => $existingThresholds,
|
'existingThresholds' => $existingThresholds,
|
||||||
'allServices' => $allServices,
|
'allServices' => $allServices,
|
||||||
'monitorServices' => $monitorServices,
|
'monitorServices' => $monitorServices,
|
||||||
|
'latestUptime' => $latestUptime,
|
||||||
'startDate' => $startDate->format('Y-m-d\T H:i'),
|
'startDate' => $startDate->format('Y-m-d\T H:i'),
|
||||||
'endDate' => $endDate->format('Y-m-d\T H:i'),
|
'endDate' => $endDate->format('Y-m-d\T H:i'),
|
||||||
'aggregation' => $aggConfig,
|
'aggregation' => $aggConfig,
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@
|
||||||
<h6 class="mb-0"><i class="fas fa-clock"></i> Время работы</h6>
|
<h6 class="mb-0"><i class="fas fa-clock"></i> Время работы</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body text-center d-flex flex-column justify-content-center">
|
<div class="card-body text-center d-flex flex-column justify-content-center">
|
||||||
{% if server.latest_metrics.uptime is defined %}
|
{% if latestUptime is defined %}
|
||||||
{% set uptime_sec = server.latest_metrics.uptime.value %}
|
{% set uptime_sec = latestUptime.value %}
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
{% if uptime_sec >= 86400 %}
|
{% if uptime_sec >= 86400 %}
|
||||||
<span class="badge bg-success fs-6 me-1">{{ (uptime_sec / 86400)|round(0, 'floor') }}д</span>
|
<span class="badge bg-success fs-6 me-1">{{ (uptime_sec / 86400)|round(0, 'floor') }}д</span>
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<i class="fas fa-play"></i> {{ server.created_at|date('d.m.Y H:i') }}
|
<i class="fas fa-play"></i> {{ server.last_seen|date('d.m.Y H:i') }}
|
||||||
</small>
|
</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
|
|
@ -581,7 +581,8 @@
|
||||||
function fetchProcesses(serverId, time) {
|
function fetchProcesses(serverId, time) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
var fullTime = time;
|
var fullTime = time;
|
||||||
if (time && time.indexOf('-') === -1) {
|
// Если нет пробела - значит только время, добавляем текущую дату
|
||||||
|
if (time && time.indexOf(' ') === -1) {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
var year = now.getFullYear();
|
var year = now.getFullYear();
|
||||||
var month = String(now.getMonth() + 1).padStart(2, '0');
|
var month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
|
|
@ -666,7 +667,7 @@ var diskTotalGB = {
|
||||||
|
|
||||||
// Графики метрик
|
// Графики метрик
|
||||||
{% for metricName, metricData in metrics %}
|
{% 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" and not (metricName starts with "temp_") %}
|
{% 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_") and metricName!="uptime" %}
|
||||||
const ctx{{ metricName|replace({'-': '_', '.': '_'}) }} = document.getElementById('chart-{{ metricName }}').getContext('2d');
|
const ctx{{ metricName|replace({'-': '_', '.': '_'}) }} = document.getElementById('chart-{{ metricName }}').getContext('2d');
|
||||||
|
|
||||||
// Подготовка данных для графика
|
// Подготовка данных для графика
|
||||||
|
|
@ -744,35 +745,25 @@ const chart{{ metricName|replace({'-': '_', '.': '_'}) }} = new Chart(ctx{{ metr
|
||||||
|
|
||||||
var dataIndex = context.tooltip._active[0].index;
|
var dataIndex = context.tooltip._active[0].index;
|
||||||
var time = labels{{ metricName }}[dataIndex];
|
var time = labels{{ metricName }}[dataIndex];
|
||||||
|
var metricValue = context.chart.data.datasets[0].data[dataIndex];
|
||||||
|
|
||||||
// Fetch processes
|
fetchProcesses({{ server.id }}, time).then(function(processLines) {
|
||||||
fetch('/api/v1/agent/' + {{ server.id }} + '/processes?time=' + encodeURIComponent(time))
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
var lines = [];
|
var lines = [];
|
||||||
lines.push('Время: ' + time);
|
lines.push('Время: ' + time);
|
||||||
{% if metricName == 'ram_used' %}
|
{% if metricName == 'ram_used' %}
|
||||||
var ramPct = data{{ metricName }}[dataIndex];
|
var ramPct = metricValue;
|
||||||
if (ramTotalGB !== null) {
|
if (ramTotalGB !== null) {
|
||||||
var ramUsed = (ramPct / 100 * ramTotalGB).toFixed(1);
|
var ramUsed = (ramPct / 100 * ramTotalGB).toFixed(1);
|
||||||
var ramFree = (ramTotalGB - ramUsed).toFixed(1);
|
var ramFree = (ramTotalGB - ramUsed).toFixed(1);
|
||||||
lines.push('Всего: ' + ramTotalGB.toFixed(1) + ' ГБ');
|
lines.push('Всего: ' + ramTotalGB.toFixed(1) + ' ГБ');
|
||||||
lines.push('Занято: ' + ramUsed + ' ГБ');
|
lines.push('Занято: ' + ramUsed + ' ГБ (' + ramPct + '%)');
|
||||||
lines.push('Свободно: ' + ramFree + ' ГБ');
|
lines.push('Свободно: ' + ramFree + ' ГБ');
|
||||||
lines.push('');
|
|
||||||
} else {
|
} else {
|
||||||
lines.push('RAM: ' + ramPct + '%');
|
lines.push('RAM: ' + ramPct + '%');
|
||||||
lines.push('(данные о памяти недоступны)');
|
lines.push('(данные о памяти недоступны)');
|
||||||
lines.push('');
|
|
||||||
}
|
|
||||||
if (data.top_ram && data.top_ram.length > 0) {
|
|
||||||
lines.push('TOP RAM:');
|
|
||||||
data.top_ram.forEach(function(proc) {
|
|
||||||
lines.push(' ' + ((proc.cmdline || '').trim() || proc.name) + ': ' + proc.value + '%');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
{% elseif metricName starts with 'disk_used_' %}
|
{% elseif metricName starts with 'disk_used_' %}
|
||||||
var diskPct = data{{ metricName }}[dataIndex];
|
var diskPct = metricValue;
|
||||||
var iface = '{{ metricName }}'.replace('disk_used_', '');
|
var iface = '{{ metricName }}'.replace('disk_used_', '');
|
||||||
var diskTotal = diskTotalGB[iface] || 0;
|
var diskTotal = diskTotalGB[iface] || 0;
|
||||||
var diskUsed = (diskPct / 100 * diskTotal).toFixed(1);
|
var diskUsed = (diskPct / 100 * diskTotal).toFixed(1);
|
||||||
|
|
@ -782,30 +773,23 @@ const chart{{ metricName|replace({'-': '_', '.': '_'}) }} = new Chart(ctx{{ metr
|
||||||
lines.push('Свободно: ' + diskFree + ' ГБ');
|
lines.push('Свободно: ' + diskFree + ' ГБ');
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if metricName == 'cpu_load' %}
|
{% if metricName == 'cpu_load' %}
|
||||||
lines.push('CPU: ' + data{{ metricName }}[dataIndex] + '%');
|
lines.push('CPU: ' + metricValue + '%');
|
||||||
if (data.top_cpu && data.top_cpu.length > 0) {
|
|
||||||
lines.push('');
|
|
||||||
lines.push('TOP CPU:');
|
|
||||||
data.top_cpu.forEach(function(proc) {
|
|
||||||
lines.push(' ' + ((proc.cmdline || '').trim() || proc.name) + ': ' + proc.value + '%');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
lines.push('Значение: ' + data{{ metricName }}[dataIndex]);
|
lines.push('Значение: ' + metricValue);
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
if (processLines.length > 0) {
|
||||||
|
lines.push('');
|
||||||
|
lines = lines.concat(processLines);
|
||||||
|
}
|
||||||
|
|
||||||
// Show tooltip
|
// Show tooltip
|
||||||
var position = context.chart.canvas.getBoundingClientRect();
|
var position = context.chart.canvas.getBoundingClientRect();
|
||||||
tooltipEl.innerHTML = lines.join('<br>');
|
tooltipEl.innerHTML = lines.join('<br>');
|
||||||
tooltipEl.style.opacity = 1;
|
tooltipEl.style.opacity = 1;
|
||||||
tooltipEl.style.left = position.left + window.pageXOffset + context.tooltip.caretX + 10 + 'px';
|
tooltipEl.style.left = position.left + window.pageXOffset + context.tooltip.caretX + 10 + 'px';
|
||||||
tooltipEl.style.top = position.top + window.pageYOffset + context.tooltip.caretY + 'px';
|
tooltipEl.style.top = position.top + window.pageYOffset + context.tooltip.caretY + 'px';
|
||||||
|
|
||||||
// Hide after 3 seconds
|
|
||||||
// setTimeout(function() {
|
|
||||||
// tooltipEl.style.opacity = 0;
|
|
||||||
// }, 3000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -848,7 +832,7 @@ chart{{ metricName|replace({'-': '_', '.': '_'}) }}.canvas.addEventListener('mou
|
||||||
// Глобальный обработчик для скрытия тултипов при уходе курсора за пределы canvas
|
// Глобальный обработчик для скрытия тултипов при уходе курсора за пределы canvas
|
||||||
document.addEventListener('mousemove', function(e) {
|
document.addEventListener('mousemove', function(e) {
|
||||||
{% for metricName, metricData in metrics %}
|
{% 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" and not (metricName starts with "temp_") %}
|
{% 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_") and metricName!="uptime" %}
|
||||||
(function() {
|
(function() {
|
||||||
var canvas = chart{{ metricName|replace({'-': '_', '.': '_'}) }}.canvas;
|
var canvas = chart{{ metricName|replace({'-': '_', '.': '_'}) }}.canvas;
|
||||||
var rect = canvas.getBoundingClientRect();
|
var rect = canvas.getBoundingClientRect();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue