Improve metrics view summaries and RAM details
This commit is contained in:
parent
a809b55b86
commit
3edf138e43
|
|
@ -317,10 +317,11 @@ class ServerDetailController extends Model
|
||||||
$stmt->execute([':id' => $id]);
|
$stmt->execute([':id' => $id]);
|
||||||
$latestUptime = $stmt->fetch();
|
$latestUptime = $stmt->fetch();
|
||||||
|
|
||||||
$simpleMetricCharts = $this->buildSimpleMetricCharts($groupedMetrics, $displayMetrics);
|
$simpleMetricCharts = $this->buildSimpleMetricCharts($groupedMetrics, $displayMetrics, $existingThresholds);
|
||||||
$networkCharts = $this->buildNetworkCharts($groupedMetrics, $displayMetrics);
|
$networkCharts = $this->buildNetworkCharts($groupedMetrics, $displayMetrics);
|
||||||
$temperatureChart = $this->buildTemperatureChart($groupedMetrics, $displayMetrics);
|
$temperatureChart = $this->buildTemperatureChart($groupedMetrics, $displayMetrics);
|
||||||
$diskCharts = $this->buildDiskCharts($groupedMetrics, $displayMetrics);
|
$diskCharts = $this->buildDiskCharts($groupedMetrics, $displayMetrics);
|
||||||
|
$summaryCards = $this->buildSummaryCards($groupedMetrics, $displayMetrics, $temperatureChart, $diskCharts, $networkCharts);
|
||||||
|
|
||||||
$templateData = [
|
$templateData = [
|
||||||
'title' => 'Сервер: ' . $server['name'],
|
'title' => 'Сервер: ' . $server['name'],
|
||||||
|
|
@ -331,6 +332,7 @@ class ServerDetailController extends Model
|
||||||
'networkCharts' => $networkCharts,
|
'networkCharts' => $networkCharts,
|
||||||
'temperatureChart' => $temperatureChart,
|
'temperatureChart' => $temperatureChart,
|
||||||
'diskCharts' => $diskCharts,
|
'diskCharts' => $diskCharts,
|
||||||
|
'summaryCards' => $summaryCards,
|
||||||
'allMetricTypes' => $allMetricTypes,
|
'allMetricTypes' => $allMetricTypes,
|
||||||
'existingThresholds' => $existingThresholds,
|
'existingThresholds' => $existingThresholds,
|
||||||
'allServices' => $allServices,
|
'allServices' => $allServices,
|
||||||
|
|
@ -399,6 +401,10 @@ class ServerDetailController extends Model
|
||||||
|
|
||||||
$expanded[$metricName] = $metricName;
|
$expanded[$metricName] = $metricName;
|
||||||
|
|
||||||
|
if ($metricName === 'ram_used') {
|
||||||
|
$expanded['ram_total_gb'] = 'ram_total_gb';
|
||||||
|
}
|
||||||
|
|
||||||
if (str_starts_with($metricName, 'disk_used_')) {
|
if (str_starts_with($metricName, 'disk_used_')) {
|
||||||
$suffix = substr($metricName, strlen('disk_used_'));
|
$suffix = substr($metricName, strlen('disk_used_'));
|
||||||
$expanded['disk_total_gb_' . $suffix] = 'disk_total_gb_' . $suffix;
|
$expanded['disk_total_gb_' . $suffix] = 'disk_total_gb_' . $suffix;
|
||||||
|
|
@ -408,7 +414,7 @@ class ServerDetailController extends Model
|
||||||
return array_values($expanded);
|
return array_values($expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildSimpleMetricCharts(array $groupedMetrics, ?array $displayMetrics): array
|
private function buildSimpleMetricCharts(array $groupedMetrics, ?array $displayMetrics, array $existingThresholds): array
|
||||||
{
|
{
|
||||||
$charts = [];
|
$charts = [];
|
||||||
$config = [
|
$config = [
|
||||||
|
|
@ -431,9 +437,13 @@ class ServerDetailController extends Model
|
||||||
'values' => $this->extractValues($groupedMetrics[$metricName]),
|
'values' => $this->extractValues($groupedMetrics[$metricName]),
|
||||||
'lastValue' => round((float)($groupedMetrics[$metricName][0]['value'] ?? 0), 2),
|
'lastValue' => round((float)($groupedMetrics[$metricName][0]['value'] ?? 0), 2),
|
||||||
'lastTime' => $this->formatPointTime($groupedMetrics[$metricName][0] ?? []),
|
'lastTime' => $this->formatPointTime($groupedMetrics[$metricName][0] ?? []),
|
||||||
|
'thresholds' => $existingThresholds[$metricName] ?? null,
|
||||||
|
'details' => $metricName === 'ram_used' ? $this->buildRamDetails($groupedMetrics) : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usort($charts, fn ($a, $b) => strnatcasecmp($a['title'], $b['title']));
|
||||||
|
|
||||||
return $charts;
|
return $charts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -490,6 +500,8 @@ class ServerDetailController extends Model
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usort($charts, fn ($a, $b) => strnatcasecmp($a['id'], $b['id']));
|
||||||
|
|
||||||
return $charts;
|
return $charts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,6 +512,8 @@ class ServerDetailController extends Model
|
||||||
$colors = ['#dc3545', '#fd7e14', '#0dcaf0', '#6f42c1', '#20c997', '#ffc107', '#6610f2', '#198754'];
|
$colors = ['#dc3545', '#fd7e14', '#0dcaf0', '#6f42c1', '#20c997', '#ffc107', '#6610f2', '#198754'];
|
||||||
$colorIndex = 0;
|
$colorIndex = 0;
|
||||||
|
|
||||||
|
$tempSeries = [];
|
||||||
|
|
||||||
foreach ($groupedMetrics as $metricName => $points) {
|
foreach ($groupedMetrics as $metricName => $points) {
|
||||||
if (!str_starts_with($metricName, 'temp_') || !$this->isMetricSelected($metricName, $displayMetrics) || empty($points)) {
|
if (!str_starts_with($metricName, 'temp_') || !$this->isMetricSelected($metricName, $displayMetrics) || empty($points)) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -509,11 +523,22 @@ class ServerDetailController extends Model
|
||||||
$labels = $this->extractLabels($points);
|
$labels = $this->extractLabels($points);
|
||||||
}
|
}
|
||||||
|
|
||||||
$datasets[] = [
|
$tempSeries[] = [
|
||||||
'label' => $this->formatMetricLabel($metricName),
|
'label' => $this->formatMetricLabel($metricName),
|
||||||
'color' => $colors[$colorIndex % count($colors)],
|
'metricName' => $metricName,
|
||||||
'values' => $this->extractValues($points),
|
'values' => $this->extractValues($points),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($tempSeries, fn ($a, $b) => strnatcasecmp($a['label'], $b['label']));
|
||||||
|
|
||||||
|
foreach ($tempSeries as $series) {
|
||||||
|
$datasets[] = [
|
||||||
|
'label' => $series['label'],
|
||||||
|
'metricName' => $series['metricName'],
|
||||||
|
'color' => $colors[$colorIndex % count($colors)],
|
||||||
|
'values' => $series['values'],
|
||||||
|
];
|
||||||
$colorIndex++;
|
$colorIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -557,9 +582,115 @@ class ServerDetailController extends Model
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usort($charts, fn ($a, $b) => strnatcasecmp($a['title'], $b['title']));
|
||||||
|
|
||||||
return $charts;
|
return $charts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildSummaryCards(array $groupedMetrics, ?array $displayMetrics, array $temperatureChart, array $diskCharts, array $networkCharts): array
|
||||||
|
{
|
||||||
|
$cards = [];
|
||||||
|
|
||||||
|
if ($this->isMetricSelected('cpu_load', $displayMetrics) && isset($groupedMetrics['cpu_load'][0]['value'])) {
|
||||||
|
$cards[] = [
|
||||||
|
'title' => 'CPU сейчас',
|
||||||
|
'value' => round((float)$groupedMetrics['cpu_load'][0]['value'], 2) . '%',
|
||||||
|
'subtitle' => $this->formatPointTime($groupedMetrics['cpu_load'][0]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isMetricSelected('ram_used', $displayMetrics) && isset($groupedMetrics['ram_used'][0]['value'])) {
|
||||||
|
$ramDetails = $this->buildRamDetails($groupedMetrics);
|
||||||
|
$cards[] = [
|
||||||
|
'title' => 'RAM сейчас',
|
||||||
|
'value' => round((float)$groupedMetrics['ram_used'][0]['value'], 2) . '%',
|
||||||
|
'subtitle' => $ramDetails
|
||||||
|
? sprintf('Всего: %.1f ГБ | Занято: %.1f ГБ | Свободно: %.1f ГБ', $ramDetails['totalGb'], $ramDetails['usedGb'], $ramDetails['freeGb'])
|
||||||
|
: $this->formatPointTime($groupedMetrics['ram_used'][0]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($temperatureChart['datasets'])) {
|
||||||
|
$hottest = null;
|
||||||
|
foreach ($temperatureChart['datasets'] as $dataset) {
|
||||||
|
$current = $dataset['values'][count($dataset['values']) - 1] ?? null;
|
||||||
|
if ($current === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($hottest === null || $current > $hottest['value']) {
|
||||||
|
$hottest = ['label' => $dataset['label'], 'value' => $current];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($hottest) {
|
||||||
|
$cards[] = [
|
||||||
|
'title' => 'Самый горячий датчик',
|
||||||
|
'value' => $hottest['value'] . '°C',
|
||||||
|
'subtitle' => $hottest['label'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($diskCharts)) {
|
||||||
|
$topDisk = $diskCharts[0];
|
||||||
|
foreach ($diskCharts as $disk) {
|
||||||
|
if ($disk['percent'] > $topDisk['percent']) {
|
||||||
|
$topDisk = $disk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cards[] = [
|
||||||
|
'title' => 'Самый занятый диск',
|
||||||
|
'value' => $topDisk['percent'] . '%',
|
||||||
|
'subtitle' => $topDisk['title'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($networkCharts)) {
|
||||||
|
$topNetwork = null;
|
||||||
|
foreach ($networkCharts as $chart) {
|
||||||
|
$peak = 0.0;
|
||||||
|
foreach ($chart['datasets'] as $dataset) {
|
||||||
|
foreach ($dataset['values'] as $value) {
|
||||||
|
$peak = max($peak, (float)$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($topNetwork === null || $peak > $topNetwork['value']) {
|
||||||
|
$topNetwork = ['label' => $chart['title'], 'value' => round($peak, 2), 'unit' => $chart['unit'] ?? ''];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($topNetwork) {
|
||||||
|
$cards[] = [
|
||||||
|
'title' => 'Самый активный интерфейс',
|
||||||
|
'value' => $topNetwork['value'] . $topNetwork['unit'],
|
||||||
|
'subtitle' => $topNetwork['label'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRamDetails(array $groupedMetrics): ?array
|
||||||
|
{
|
||||||
|
if (!isset($groupedMetrics['ram_used'][0]['value'], $groupedMetrics['ram_total_gb'][0]['value'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$percentUsed = (float)$groupedMetrics['ram_used'][0]['value'];
|
||||||
|
$totalGb = (float)$groupedMetrics['ram_total_gb'][0]['value'];
|
||||||
|
if ($totalGb <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$usedGb = round(($percentUsed / 100) * $totalGb, 1);
|
||||||
|
$freeGb = round($totalGb - $usedGb, 1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'totalGb' => round($totalGb, 1),
|
||||||
|
'usedGb' => $usedGb,
|
||||||
|
'freeGb' => $freeGb,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function isMetricSelected(string $metricName, ?array $displayMetrics): bool
|
private function isMetricSelected(string $metricName, ?array $displayMetrics): bool
|
||||||
{
|
{
|
||||||
return is_array($displayMetrics) && in_array($metricName, $displayMetrics, true);
|
return is_array($displayMetrics) && in_array($metricName, $displayMetrics, true);
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if summaryCards is not empty %}
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
{% for card in summaryCards %}
|
||||||
|
<div class="col-md-6 col-xl">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="small text-muted mb-1">{{ card.title }}</div>
|
||||||
|
<div class="fw-semibold">{{ card.value }}</div>
|
||||||
|
<div class="small text-muted mt-1">{{ card.subtitle }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if not displayMetrics or displayMetrics is empty %}
|
{% if not displayMetrics or displayMetrics is empty %}
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
<i class="fas fa-info-circle"></i>
|
<i class="fas fa-info-circle"></i>
|
||||||
|
|
@ -140,6 +156,14 @@
|
||||||
<h6 class="mb-0">{{ chart.title }}</h6>
|
<h6 class="mb-0">{{ chart.title }}</h6>
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
<div class="fw-semibold">{{ chart.lastValue }}{{ chart.unit }}</div>
|
<div class="fw-semibold">{{ chart.lastValue }}{{ chart.unit }}</div>
|
||||||
|
{% if chart.id == 'ram_used' and chart.details %}
|
||||||
|
<div class="small text-muted">
|
||||||
|
Всего: {{ chart.details.totalGb }} ГБ | Занято: {{ chart.details.usedGb }} ГБ
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">
|
||||||
|
Свободно: {{ chart.details.freeGb }} ГБ
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="small text-muted">{{ chart.lastTime }}</div>
|
<div class="small text-muted">{{ chart.lastTime }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -490,6 +514,16 @@ function renderProcessTooltip(context, chartMeta) {
|
||||||
|
|
||||||
fetchProcesses({{ server.id }}, timestamp).then(function(data) {
|
fetchProcesses({{ server.id }}, timestamp).then(function(data) {
|
||||||
const lines = [`Время: ${chartMeta.labels[dataIndex]}`, valueText];
|
const lines = [`Время: ${chartMeta.labels[dataIndex]}`, valueText];
|
||||||
|
|
||||||
|
if (chartMeta.id === 'ram_used' && chartMeta.details && chartMeta.details.totalGb) {
|
||||||
|
const totalGb = Number(chartMeta.details.totalGb);
|
||||||
|
const usedGb = Number(((Number(value) / 100) * totalGb).toFixed(1));
|
||||||
|
const freeGb = Number((totalGb - usedGb).toFixed(1));
|
||||||
|
lines.push(`Всего: ${totalGb.toFixed(1)} ГБ`);
|
||||||
|
lines.push(`Занято: ${usedGb.toFixed(1)} ГБ`);
|
||||||
|
lines.push(`Свободно: ${freeGb.toFixed(1)} ГБ`);
|
||||||
|
}
|
||||||
|
|
||||||
const processList = chartMeta.id === 'cpu_load' ? (data.top_cpu || []) : (data.top_ram || []);
|
const processList = chartMeta.id === 'cpu_load' ? (data.top_cpu || []) : (data.top_ram || []);
|
||||||
|
|
||||||
if (processList.length > 0) {
|
if (processList.length > 0) {
|
||||||
|
|
@ -611,10 +645,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
const simpleMetricCharts = {{ simpleMetricCharts|json_encode|raw }};
|
const simpleMetricCharts = {{ simpleMetricCharts|json_encode|raw }};
|
||||||
simpleMetricCharts.forEach(function(chart) {
|
simpleMetricCharts.forEach(function(chart) {
|
||||||
createLineChart(
|
const datasets = [{
|
||||||
'chart-' + chart.id,
|
|
||||||
chart.labels,
|
|
||||||
[{
|
|
||||||
label: chart.title,
|
label: chart.title,
|
||||||
data: chart.values,
|
data: chart.values,
|
||||||
borderColor: chart.color,
|
borderColor: chart.color,
|
||||||
|
|
@ -623,13 +654,47 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
tension: 0.2,
|
tension: 0.2,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
borderWidth: 2
|
borderWidth: 2
|
||||||
}],
|
}];
|
||||||
|
|
||||||
|
if (chart.thresholds && chart.thresholds.warning !== null) {
|
||||||
|
datasets.push({
|
||||||
|
label: 'Warning',
|
||||||
|
data: chart.labels.map(() => Number(chart.thresholds.warning)),
|
||||||
|
borderColor: '#fd7e14',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
fill: false,
|
||||||
|
tension: 0,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderDash: [6, 4]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chart.thresholds && chart.thresholds.critical !== null) {
|
||||||
|
datasets.push({
|
||||||
|
label: 'Critical',
|
||||||
|
data: chart.labels.map(() => Number(chart.thresholds.critical)),
|
||||||
|
borderColor: '#dc3545',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
fill: false,
|
||||||
|
tension: 0,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderDash: [3, 3]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createLineChart(
|
||||||
|
'chart-' + chart.id,
|
||||||
|
chart.labels,
|
||||||
|
datasets,
|
||||||
chart.unit,
|
chart.unit,
|
||||||
{
|
{
|
||||||
id: chart.id,
|
id: chart.id,
|
||||||
labels: chart.labels,
|
labels: chart.labels,
|
||||||
timestamps: chart.timestamps || [],
|
timestamps: chart.timestamps || [],
|
||||||
unit: chart.unit
|
unit: chart.unit,
|
||||||
|
details: chart.details || null
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue