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]);
|
||||
$latestUptime = $stmt->fetch();
|
||||
|
||||
$simpleMetricCharts = $this->buildSimpleMetricCharts($groupedMetrics, $displayMetrics);
|
||||
$simpleMetricCharts = $this->buildSimpleMetricCharts($groupedMetrics, $displayMetrics, $existingThresholds);
|
||||
$networkCharts = $this->buildNetworkCharts($groupedMetrics, $displayMetrics);
|
||||
$temperatureChart = $this->buildTemperatureChart($groupedMetrics, $displayMetrics);
|
||||
$diskCharts = $this->buildDiskCharts($groupedMetrics, $displayMetrics);
|
||||
$summaryCards = $this->buildSummaryCards($groupedMetrics, $displayMetrics, $temperatureChart, $diskCharts, $networkCharts);
|
||||
|
||||
$templateData = [
|
||||
'title' => 'Сервер: ' . $server['name'],
|
||||
|
|
@ -331,6 +332,7 @@ class ServerDetailController extends Model
|
|||
'networkCharts' => $networkCharts,
|
||||
'temperatureChart' => $temperatureChart,
|
||||
'diskCharts' => $diskCharts,
|
||||
'summaryCards' => $summaryCards,
|
||||
'allMetricTypes' => $allMetricTypes,
|
||||
'existingThresholds' => $existingThresholds,
|
||||
'allServices' => $allServices,
|
||||
|
|
@ -399,6 +401,10 @@ class ServerDetailController extends Model
|
|||
|
||||
$expanded[$metricName] = $metricName;
|
||||
|
||||
if ($metricName === 'ram_used') {
|
||||
$expanded['ram_total_gb'] = 'ram_total_gb';
|
||||
}
|
||||
|
||||
if (str_starts_with($metricName, 'disk_used_')) {
|
||||
$suffix = substr($metricName, strlen('disk_used_'));
|
||||
$expanded['disk_total_gb_' . $suffix] = 'disk_total_gb_' . $suffix;
|
||||
|
|
@ -408,7 +414,7 @@ class ServerDetailController extends Model
|
|||
return array_values($expanded);
|
||||
}
|
||||
|
||||
private function buildSimpleMetricCharts(array $groupedMetrics, ?array $displayMetrics): array
|
||||
private function buildSimpleMetricCharts(array $groupedMetrics, ?array $displayMetrics, array $existingThresholds): array
|
||||
{
|
||||
$charts = [];
|
||||
$config = [
|
||||
|
|
@ -431,9 +437,13 @@ class ServerDetailController extends Model
|
|||
'values' => $this->extractValues($groupedMetrics[$metricName]),
|
||||
'lastValue' => round((float)($groupedMetrics[$metricName][0]['value'] ?? 0), 2),
|
||||
'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;
|
||||
}
|
||||
|
||||
|
|
@ -490,6 +500,8 @@ class ServerDetailController extends Model
|
|||
];
|
||||
}
|
||||
|
||||
usort($charts, fn ($a, $b) => strnatcasecmp($a['id'], $b['id']));
|
||||
|
||||
return $charts;
|
||||
}
|
||||
|
||||
|
|
@ -500,6 +512,8 @@ class ServerDetailController extends Model
|
|||
$colors = ['#dc3545', '#fd7e14', '#0dcaf0', '#6f42c1', '#20c997', '#ffc107', '#6610f2', '#198754'];
|
||||
$colorIndex = 0;
|
||||
|
||||
$tempSeries = [];
|
||||
|
||||
foreach ($groupedMetrics as $metricName => $points) {
|
||||
if (!str_starts_with($metricName, 'temp_') || !$this->isMetricSelected($metricName, $displayMetrics) || empty($points)) {
|
||||
continue;
|
||||
|
|
@ -509,11 +523,22 @@ class ServerDetailController extends Model
|
|||
$labels = $this->extractLabels($points);
|
||||
}
|
||||
|
||||
$datasets[] = [
|
||||
$tempSeries[] = [
|
||||
'label' => $this->formatMetricLabel($metricName),
|
||||
'color' => $colors[$colorIndex % count($colors)],
|
||||
'metricName' => $metricName,
|
||||
'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++;
|
||||
}
|
||||
|
||||
|
|
@ -557,9 +582,115 @@ class ServerDetailController extends Model
|
|||
];
|
||||
}
|
||||
|
||||
usort($charts, fn ($a, $b) => strnatcasecmp($a['title'], $b['title']));
|
||||
|
||||
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
|
||||
{
|
||||
return is_array($displayMetrics) && in_array($metricName, $displayMetrics, true);
|
||||
|
|
|
|||
|
|
@ -122,6 +122,22 @@
|
|||
</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 %}
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
|
|
@ -140,6 +156,14 @@
|
|||
<h6 class="mb-0">{{ chart.title }}</h6>
|
||||
<div class="text-end">
|
||||
<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>
|
||||
</div>
|
||||
|
|
@ -490,6 +514,16 @@ function renderProcessTooltip(context, chartMeta) {
|
|||
|
||||
fetchProcesses({{ server.id }}, timestamp).then(function(data) {
|
||||
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 || []);
|
||||
|
||||
if (processList.length > 0) {
|
||||
|
|
@ -611,25 +645,56 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
|
||||
const simpleMetricCharts = {{ simpleMetricCharts|json_encode|raw }};
|
||||
simpleMetricCharts.forEach(function(chart) {
|
||||
const datasets = [{
|
||||
label: chart.title,
|
||||
data: chart.values,
|
||||
borderColor: chart.color,
|
||||
backgroundColor: chart.color + '22',
|
||||
fill: true,
|
||||
tension: 0.2,
|
||||
pointRadius: 0,
|
||||
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,
|
||||
[{
|
||||
label: chart.title,
|
||||
data: chart.values,
|
||||
borderColor: chart.color,
|
||||
backgroundColor: chart.color + '22',
|
||||
fill: true,
|
||||
tension: 0.2,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2
|
||||
}],
|
||||
datasets,
|
||||
chart.unit,
|
||||
{
|
||||
id: chart.id,
|
||||
labels: chart.labels,
|
||||
timestamps: chart.timestamps || [],
|
||||
unit: chart.unit
|
||||
unit: chart.unit,
|
||||
details: chart.details || null
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue