updated monitoring.html

This commit is contained in:
2026-02-16 11:44:29 +01:00
parent 717d173408
commit 4af2b1b560
+170 -75
View File
@@ -1,6 +1,14 @@
{{define "monitoring"}} {{define "monitoring"}}
{{template "layout_start" .}} {{template "layout_start" .}}
<style>
/* Monitoring page specific overrides */
.sysinfo-value {
text-align: right;
font-weight: bold;
}
</style>
<div class="page-header"> <div class="page-header">
<h2>Rendszermonitor</h2> <h2>Rendszermonitor</h2>
</div> </div>
@@ -145,15 +153,37 @@
<script src="/static/chart.min.js"></script> <script src="/static/chart.min.js"></script>
<script> <script>
(function() { (function() {
'use strict';
// --- Chart.js dark theme defaults --- // --- Chart.js dark theme defaults ---
const colors = { var colors = {
cpu: {border: '#0088cc', bg: 'rgba(0,136,204,0.1)'}, cpu: {border: '#0088cc', bg: 'rgba(0,136,204,0.1)'},
memory: {border: '#238636', bg: 'rgba(35,134,54,0.1)'}, memory: {border: '#238636', bg: 'rgba(35,134,54,0.1)'},
temp: {border: '#d29922', bg: 'rgba(210,153,34,0.1)'}, temp: {border: '#d29922', bg: 'rgba(210,153,34,0.1)'},
load: {border: '#db6d28', bg: 'rgba(219,109,40,0.1)'}, load: {border: '#db6d28', bg: 'rgba(219,109,40,0.1)'}
}; };
const chartOpts = (yLabel, beginAtZero) => ({ // --- Range helper: returns milliseconds for a range string ---
function parseRangeMs(range) {
switch (range) {
case '1h': return 3600000;
case '6h': return 21600000;
case '24h': return 86400000;
case '7d': return 604800000;
case '30d': return 2592000000;
default: return 3600000;
}
}
// --- X-axis tick label format: short time for <=24h, date for longer ---
function tickFormatForRange(range) {
if (range === '7d' || range === '30d') return 'date';
return 'time';
}
// --- Chart options for line charts with LINEAR x-axis ---
function chartOpts(yLabel, beginAtZero) {
return {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
animation: {duration: 300}, animation: {duration: 300},
@@ -168,15 +198,23 @@
callbacks: { callbacks: {
title: function(items) { title: function(items) {
if (!items.length) return ''; if (!items.length) return '';
return formatTimestamp(items[0].label); // parsed.x is the ms timestamp on a linear axis
return formatTimestamp(items[0].parsed.x);
} }
} }
} }
}, },
scales: { scales: {
x: { x: {
type: 'linear',
grid: {color: 'rgba(48,54,61,0.5)'}, grid: {color: 'rgba(48,54,61,0.5)'},
ticks: {color: '#8b949e', maxTicksLimit: 8, callback: function(v) { return formatTimeLabel(this.getLabelForValue(v)); }} ticks: {
color: '#8b949e',
maxTicksLimit: 8,
callback: function(v) {
return formatTimeLabel(v);
}
}
}, },
y: { y: {
grid: {color: 'rgba(48,54,61,0.5)'}, grid: {color: 'rgba(48,54,61,0.5)'},
@@ -185,9 +223,11 @@
title: {display: !!yLabel, text: yLabel || '', color: '#6e7681', font: {size: 11}} title: {display: !!yLabel, text: yLabel || '', color: '#6e7681', font: {size: 11}}
} }
} }
}); };
}
const lineDataset = (color) => ({ var lineDataset = function(color) {
return {
borderColor: color.border, borderColor: color.border,
backgroundColor: color.bg, backgroundColor: color.bg,
borderWidth: 2, borderWidth: 2,
@@ -196,30 +236,69 @@
tension: 0.3, tension: 0.3,
fill: true, fill: true,
spanGaps: true spanGaps: true
}); };
};
// --- Timezone formatting --- // --- Timezone formatting ---
const budaTZ = 'Europe/Budapest'; var budaTZ = 'Europe/Budapest';
// Current range for choosing date vs time format in tick labels
var currentTickFormat = 'time';
function formatTimestamp(ts) { function formatTimestamp(ts) {
if (!ts) return ''; if (ts === null || ts === undefined || ts === '') return '';
const d = new Date(typeof ts === 'number' && ts < 1e12 ? ts * 1000 : ts); if (typeof ts === 'string') ts = Number(ts);
if (isNaN(ts)) return '';
// ts should already be in ms (linear axis stores ms values)
var d = new Date(ts);
return d.toLocaleString('hu-HU', {timeZone: budaTZ, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'}); return d.toLocaleString('hu-HU', {timeZone: budaTZ, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'});
} }
function formatTimeLabel(ts) { function formatTimeLabel(ts) {
if (!ts) return ''; if (ts === null || ts === undefined || ts === '') return '';
const d = new Date(typeof ts === 'number' && ts < 1e12 ? ts * 1000 : ts); if (typeof ts === 'string') ts = Number(ts);
if (isNaN(ts)) return '';
var d = new Date(ts);
if (currentTickFormat === 'date') {
return d.toLocaleDateString('hu-HU', {timeZone: budaTZ, month: '2-digit', day: '2-digit'});
}
return d.toLocaleTimeString('hu-HU', {timeZone: budaTZ, hour: '2-digit', minute: '2-digit'}); return d.toLocaleTimeString('hu-HU', {timeZone: budaTZ, hour: '2-digit', minute: '2-digit'});
} }
// --- System charts --- // --- Shared: build {x, y} point array from timestamps + values ---
let systemRange = '1h'; function buildXYData(timestamps, values) {
let chartCPU, chartMem, chartTemp, chartLoad; var points = [];
for (var i = 0; i < timestamps.length; i++) {
points.push({x: timestamps[i], y: values[i]});
}
return points;
}
// --- Shared: set x-axis min/max on a chart based on range ---
function setChartXBounds(chart, range) {
var now = Date.now();
var rangeMs = parseRangeMs(range);
chart.options.scales.x.min = now - rangeMs;
chart.options.scales.x.max = now;
}
// --- Shared: update a line chart with {x, y} data ---
function updateLineChart(chart, timestamps, values) {
chart.data.datasets[0].data = buildXYData(timestamps, values);
chart.update('none');
}
// =============================================
// SYSTEM CHARTS
// =============================================
var systemRange = '1h';
var chartCPU, chartMem, chartTemp, chartLoad;
function initSystemCharts() { function initSystemCharts() {
const mkChart = (id, color, yLabel, beginAtZero) => { var mkChart = function(id, color, yLabel, beginAtZero) {
return new Chart(document.getElementById(id), { return new Chart(document.getElementById(id), {
type: 'line', type: 'line',
data: {labels: [], datasets: [{data: [], ...lineDataset(color)}]}, data: {datasets: [{data: [], ...lineDataset(color)}]},
options: chartOpts(yLabel, beginAtZero) options: chartOpts(yLabel, beginAtZero)
}); });
}; };
@@ -231,10 +310,10 @@
async function loadSystemMetrics() { async function loadSystemMetrics() {
try { try {
const resp = await fetch('/api/metrics/system?range=' + systemRange + '&resolution=200'); var resp = await fetch('/api/metrics/system?range=' + systemRange + '&resolution=200');
const json = await resp.json(); var json = await resp.json();
if (!json.ok || !json.data) return; if (!json.ok || !json.data) return;
const d = json.data; var d = json.data;
if (!d.labels || d.labels.length === 0) { if (!d.labels || d.labels.length === 0) {
document.getElementById('system-charts').style.display = 'none'; document.getElementById('system-charts').style.display = 'none';
@@ -244,18 +323,21 @@
document.getElementById('system-charts').style.display = ''; document.getElementById('system-charts').style.display = '';
document.getElementById('system-charts-empty').style.display = 'none'; document.getElementById('system-charts-empty').style.display = 'none';
const labels = d.labels.map(ts => ts * 1000); // ms for Date // Convert Unix seconds to milliseconds
var timestamps = d.labels.map(function(ts) { return ts * 1000; });
function updateChart(chart, labels, data) { // Update tick label format based on range
chart.data.labels = labels; currentTickFormat = tickFormatForRange(systemRange);
chart.data.datasets[0].data = data;
chart.update('none');
}
updateChart(chartCPU, labels, d.cpu); // Set x-axis bounds to the full requested range
updateChart(chartMem, labels, d.memory); var allCharts = [chartCPU, chartMem, chartTemp, chartLoad];
updateChart(chartTemp, labels, d.temp); allCharts.forEach(function(c) { setChartXBounds(c, systemRange); });
updateChart(chartLoad, labels, d.load1);
// Update each chart with {x, y} data
updateLineChart(chartCPU, timestamps, d.cpu);
updateLineChart(chartMem, timestamps, d.memory);
updateLineChart(chartTemp, timestamps, d.temp);
updateLineChart(chartLoad, timestamps, d.load1);
} catch(e) { } catch(e) {
console.error('Failed to load system metrics:', e); console.error('Failed to load system metrics:', e);
} }
@@ -263,20 +345,23 @@
// Range bar clicks // Range bar clicks
document.getElementById('system-range-bar').addEventListener('click', function(e) { document.getElementById('system-range-bar').addEventListener('click', function(e) {
const btn = e.target.closest('.filter-btn'); var btn = e.target.closest('.filter-btn');
if (!btn) return; if (!btn) return;
this.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); this.querySelectorAll('.filter-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active'); btn.classList.add('active');
systemRange = btn.dataset.range; systemRange = btn.dataset.range;
loadSystemMetrics(); loadSystemMetrics();
}); });
// --- Container bar charts --- // =============================================
let chartContainerCPU, chartContainerMem; // CONTAINER BAR CHARTS
let containerNames = []; // =============================================
var chartContainerCPU, chartContainerMem;
var containerNames = [];
function initContainerCharts() { function initContainerCharts() {
const barOpts = (xLabel) => ({ var barOpts = function(xLabel) {
return {
indexAxis: 'y', indexAxis: 'y',
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
@@ -305,13 +390,14 @@
}, },
onClick: function(evt, elements) { onClick: function(evt, elements) {
if (elements.length > 0) { if (elements.length > 0) {
const idx = elements[0].index; var idx = elements[0].index;
if (containerNames[idx]) { if (containerNames[idx]) {
showContainerDetail(containerNames[idx]); showContainerDetail(containerNames[idx]);
} }
} }
} }
}); };
};
chartContainerCPU = new Chart(document.getElementById('chart-container-cpu'), { chartContainerCPU = new Chart(document.getElementById('chart-container-cpu'), {
type: 'bar', type: 'bar',
@@ -327,11 +413,11 @@
async function loadContainerSummary() { async function loadContainerSummary() {
try { try {
const resp = await fetch('/api/metrics/containers/summary'); var resp = await fetch('/api/metrics/containers/summary');
const json = await resp.json(); var json = await resp.json();
if (!json.ok || !json.data) return; if (!json.ok || !json.data) return;
const data = json.data; var data = json.data;
if (!data.length) { if (!data.length) {
document.getElementById('container-charts').style.display = 'none'; document.getElementById('container-charts').style.display = 'none';
document.getElementById('container-charts-empty').style.display = 'block'; document.getElementById('container-charts-empty').style.display = 'block';
@@ -340,13 +426,13 @@
document.getElementById('container-charts').style.display = ''; document.getElementById('container-charts').style.display = '';
document.getElementById('container-charts-empty').style.display = 'none'; document.getElementById('container-charts-empty').style.display = 'none';
containerNames = data.map(c => c.name); containerNames = data.map(function(c) { return c.name; });
const cpuData = data.map(c => Math.round(c.cpu_percent * 100) / 100); var cpuData = data.map(function(c) { return Math.round(c.cpu_percent * 100) / 100; });
const memData = data.map(c => Math.round(c.mem_usage_mb)); var memData = data.map(function(c) { return Math.round(c.mem_usage_mb); });
// Adjust bar chart height based on container count // Adjust bar chart height based on container count
const h = Math.max(200, data.length * 35 + 60); var h = Math.max(200, data.length * 35 + 60);
document.querySelectorAll('.chart-wrap-bar').forEach(el => el.style.height = h + 'px'); document.querySelectorAll('.chart-wrap-bar').forEach(function(el) { el.style.height = h + 'px'; });
chartContainerCPU.data.labels = containerNames; chartContainerCPU.data.labels = containerNames;
chartContainerCPU.data.datasets[0].data = cpuData; chartContainerCPU.data.datasets[0].data = cpuData;
@@ -360,16 +446,18 @@
} }
} }
// --- Container detail --- // =============================================
let detailChartCPU, detailChartMem; // CONTAINER DETAIL (per-container history)
let detailContainer = ''; // =============================================
let detailRange = '1h'; var detailChartCPU, detailChartMem;
var detailContainer = '';
var detailRange = '1h';
function initDetailCharts() { function initDetailCharts() {
const mkChart = (id, color, yLabel) => { var mkChart = function(id, color, yLabel) {
return new Chart(document.getElementById(id), { return new Chart(document.getElementById(id), {
type: 'line', type: 'line',
data: {labels: [], datasets: [{data: [], ...lineDataset(color)}]}, data: {datasets: [{data: [], ...lineDataset(color)}]},
options: chartOpts(yLabel, true) options: chartOpts(yLabel, true)
}); });
}; };
@@ -393,46 +481,50 @@
async function loadContainerDetail() { async function loadContainerDetail() {
if (!detailContainer) return; if (!detailContainer) return;
try { try {
const resp = await fetch('/api/metrics/containers/' + encodeURIComponent(detailContainer) + '?range=' + detailRange + '&resolution=150'); var resp = await fetch('/api/metrics/containers/' + encodeURIComponent(detailContainer) + '?range=' + detailRange + '&resolution=150');
const json = await resp.json(); var json = await resp.json();
if (!json.ok || !json.data) return; if (!json.ok || !json.data) return;
const d = json.data; var d = json.data;
const labels = (d.labels || []).map(ts => ts * 1000);
detailChartCPU.data.labels = labels; // Convert Unix seconds to milliseconds
detailChartCPU.data.datasets[0].data = d.cpu || []; var timestamps = (d.labels || []).map(function(ts) { return ts * 1000; });
detailChartCPU.update('none');
detailChartMem.data.labels = labels; // Set x-axis bounds
detailChartMem.data.datasets[0].data = d.memory || []; setChartXBounds(detailChartCPU, detailRange);
detailChartMem.update('none'); setChartXBounds(detailChartMem, detailRange);
// Update with {x, y} data
updateLineChart(detailChartCPU, timestamps, d.cpu || []);
updateLineChart(detailChartMem, timestamps, d.memory || []);
} catch(e) { } catch(e) {
console.error('Failed to load container detail:', e); console.error('Failed to load container detail:', e);
} }
} }
document.getElementById('container-range-bar').addEventListener('click', function(e) { document.getElementById('container-range-bar').addEventListener('click', function(e) {
const btn = e.target.closest('.filter-btn'); var btn = e.target.closest('.filter-btn');
if (!btn) return; if (!btn) return;
this.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); this.querySelectorAll('.filter-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active'); btn.classList.add('active');
detailRange = btn.dataset.range; detailRange = btn.dataset.range;
loadContainerDetail(); loadContainerDetail();
}); });
// --- Static system info --- // =============================================
// STATIC SYSTEM INFO
// =============================================
async function loadSysInfo() { async function loadSysInfo() {
try { try {
const resp = await fetch('/api/metrics/sysinfo'); var resp = await fetch('/api/metrics/sysinfo');
const json = await resp.json(); var json = await resp.json();
if (!json.ok || !json.data) return; if (!json.ok || !json.data) return;
const d = json.data; var d = json.data;
document.getElementById('sysinfo-hostname').textContent = d.hostname || ''; document.getElementById('sysinfo-hostname').textContent = d.hostname || '';
document.getElementById('sysinfo-os').textContent = d.os || ''; document.getElementById('sysinfo-os').textContent = d.os || '';
document.getElementById('sysinfo-kernel').textContent = d.kernel || ''; document.getElementById('sysinfo-kernel').textContent = d.kernel || '';
let cpuText = d.cpu_model || ''; var cpuText = d.cpu_model || '';
if (d.cpu_cores > 0) cpuText += ' (' + d.cpu_cores + ' mag)'; if (d.cpu_cores > 0) cpuText += ' (' + d.cpu_cores + ' mag)';
document.getElementById('sysinfo-cpu').textContent = cpuText; document.getElementById('sysinfo-cpu').textContent = cpuText;
@@ -440,7 +532,7 @@
document.getElementById('sysinfo-uptime').textContent = formatUptime(d.uptime_seconds); document.getElementById('sysinfo-uptime').textContent = formatUptime(d.uptime_seconds);
} }
if (d.boot_time) { if (d.boot_time) {
const bt = new Date(d.boot_time); var bt = new Date(d.boot_time);
document.getElementById('sysinfo-boot').textContent = bt.toLocaleString('hu-HU', {timeZone: budaTZ, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'}); document.getElementById('sysinfo-boot').textContent = bt.toLocaleString('hu-HU', {timeZone: budaTZ, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'});
} }
} catch(e) { } catch(e) {
@@ -449,15 +541,17 @@
} }
function formatUptime(seconds) { function formatUptime(seconds) {
const days = Math.floor(seconds / 86400); var days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600); var hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60); var minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) return days + ' nap, ' + hours + ' óra'; if (days > 0) return days + ' nap, ' + hours + ' óra';
if (hours > 0) return hours + ' óra, ' + minutes + ' perc'; if (hours > 0) return hours + ' óra, ' + minutes + ' perc';
return minutes + ' perc'; return minutes + ' perc';
} }
// --- Init --- // =============================================
// INIT
// =============================================
initSystemCharts(); initSystemCharts();
initContainerCharts(); initContainerCharts();
initDetailCharts(); initDetailCharts();
@@ -471,6 +565,7 @@
loadContainerSummary(); loadContainerSummary();
if (detailContainer) loadContainerDetail(); if (detailContainer) loadContainerDetail();
}, 60000); }, 60000);
})(); })();
</script> </script>