4923afa6a7
- Add StorageBars to backupsHandler so all registered storage paths appear - Update backups.html to use StorageBars loop (replacing single HDDConfigured block) - Rename "SSD (/)" → "Rendszer (/)" on backup, monitoring, and dashboard pages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
617 lines
24 KiB
HTML
617 lines
24 KiB
HTML
{{define "monitoring"}}
|
||
{{template "layout_start" .}}
|
||
|
||
<div class="page-header">
|
||
<h2>Rendszermonitor</h2>
|
||
</div>
|
||
|
||
<!-- Section 1: System Overview -->
|
||
<div class="monitor-card">
|
||
<h3>Rendszer áttekintés</h3>
|
||
<div class="sysinfo-grid">
|
||
<div class="sysinfo-row">
|
||
<span class="sysinfo-label">Gépnév</span>
|
||
<span class="sysinfo-value" id="sysinfo-hostname">–</span>
|
||
</div>
|
||
<div class="sysinfo-row">
|
||
<span class="sysinfo-label">Operációs rendszer</span>
|
||
<span class="sysinfo-value" id="sysinfo-os">–</span>
|
||
</div>
|
||
<div class="sysinfo-row">
|
||
<span class="sysinfo-label">Kernel</span>
|
||
<span class="sysinfo-value" id="sysinfo-kernel">–</span>
|
||
</div>
|
||
<div class="sysinfo-row">
|
||
<span class="sysinfo-label">Processzor</span>
|
||
<span class="sysinfo-value" id="sysinfo-cpu">–</span>
|
||
</div>
|
||
<div class="sysinfo-row">
|
||
<span class="sysinfo-label">Üzemidő</span>
|
||
<span class="sysinfo-value" id="sysinfo-uptime">–</span>
|
||
</div>
|
||
<div class="sysinfo-row">
|
||
<span class="sysinfo-label">Indítás</span>
|
||
<span class="sysinfo-value" id="sysinfo-boot">–</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 1.5: Storage (moved here for visibility) -->
|
||
<div class="monitor-card">
|
||
<h3>Tárhely</h3>
|
||
<div class="storage-bars">
|
||
{{with .SystemInfo}}
|
||
<div class="storage-item">
|
||
<div class="storage-header">
|
||
<span class="storage-label">Rendszer (/)</span>
|
||
<span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span>
|
||
</div>
|
||
<div class="system-bar">
|
||
<div class="system-bar-fill {{usageColor .DiskPercent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .DiskPercent}}%"></div>
|
||
</div>
|
||
</div>
|
||
{{range $.StorageBars}}
|
||
<div class="storage-item">
|
||
<div class="storage-header">
|
||
<span class="storage-label">{{.Label}}</span>
|
||
<span class="storage-value">{{fmtGB .UsedGB}} / {{fmtGB .TotalGB}} ({{printf "%.0f" .Percent}}%)</span>
|
||
</div>
|
||
<div class="system-bar">
|
||
<div class="system-bar-fill {{usageColor .Percent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .Percent}}%"></div>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
{{end}}
|
||
</div>
|
||
{{if .DiskWarnings}}
|
||
<div class="inline-warnings">
|
||
{{range .DiskWarnings}}
|
||
<div class="inline-warning inline-warning-{{.Level}}">
|
||
<span class="inline-warning-dot">●</span>
|
||
<span class="inline-warning-text">{{.Message}}</span>
|
||
{{if .Link}}<a href="{{.Link}}" class="inline-warning-link">{{.LinkText}} →</a>{{end}}
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
<!-- Section 2: Remote Monitoring Status -->
|
||
<div class="monitor-card">
|
||
<h3>Távoli monitoring</h3>
|
||
{{if not .MonitoringEnabled}}
|
||
<div class="monitoring-banner monitoring-banner-red">
|
||
⚠️ A távoli monitoring ki van kapcsolva. Az üzemeltető nem kap értesítést hibák esetén.
|
||
</div>
|
||
{{else}}
|
||
{{if .AllPingsConfigured}}
|
||
<div class="monitoring-banner monitoring-banner-green">
|
||
✅ Minden távoli monitoring aktív — az üzemeltető értesítést kap hibák esetén.
|
||
</div>
|
||
{{else}}
|
||
<div class="monitoring-banner monitoring-banner-yellow">
|
||
⚠️ Egyes monitoring ellenőrzések nincsenek beállítva. Kérd az üzemeltetőt a konfiguráláshoz.
|
||
</div>
|
||
{{end}}
|
||
<div class="sysinfo-grid" style="margin-top: 0.75rem">
|
||
{{range .PingStatus}}
|
||
<div class="sysinfo-row">
|
||
<span class="sysinfo-label">{{.Icon}} {{.Label}}</span>
|
||
<span class="sysinfo-value">
|
||
{{if .Configured}}<span class="ping-status-ok">✅ Beállítva</span>{{else}}<span class="ping-status-warn">⚠️ Nincs beállítva</span>{{end}}
|
||
<span class="ping-schedule">{{.Schedule}}</span>
|
||
</span>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
<!-- Section 3: System Metrics Charts -->
|
||
<div class="monitor-card">
|
||
<div class="monitor-card-header">
|
||
<h3>Rendszer metrikák</h3>
|
||
<div class="time-range-bar" id="system-range-bar">
|
||
<button class="filter-btn active" data-range="1h">1 óra</button>
|
||
<button class="filter-btn" data-range="6h">6 óra</button>
|
||
<button class="filter-btn" data-range="24h">24 óra</button>
|
||
<button class="filter-btn" data-range="7d">7 nap</button>
|
||
<button class="filter-btn" data-range="30d">30 nap</button>
|
||
</div>
|
||
</div>
|
||
<div class="charts-grid" id="system-charts">
|
||
<div class="chart-box">
|
||
<div class="chart-title">CPU használat (%)</div>
|
||
<div class="chart-wrap"><canvas id="chart-cpu"></canvas></div>
|
||
</div>
|
||
<div class="chart-box">
|
||
<div class="chart-title">Memória használat (GB)</div>
|
||
<div class="chart-wrap"><canvas id="chart-memory"></canvas></div>
|
||
</div>
|
||
<div class="chart-box">
|
||
<div class="chart-title">Hőmérséklet (°C)</div>
|
||
<div class="chart-wrap"><canvas id="chart-temp"></canvas></div>
|
||
</div>
|
||
<div class="chart-box">
|
||
<div class="chart-title">Terhelés (Load Average)</div>
|
||
<div class="chart-wrap"><canvas id="chart-load"></canvas></div>
|
||
</div>
|
||
</div>
|
||
<div class="chart-empty" id="system-charts-empty" style="display:none">
|
||
Még nincsenek adatok. A metrikák gyűjtése elindult, az első adatok néhány perc múlva megjelennek.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 4: Container Resources -->
|
||
<div class="monitor-card">
|
||
<h3>Alkalmazás erőforrások</h3>
|
||
<div class="container-charts-row" id="container-charts">
|
||
<div class="chart-box chart-box-half">
|
||
<div class="chart-title">CPU használat (%)</div>
|
||
<div class="chart-wrap chart-wrap-bar"><canvas id="chart-container-cpu"></canvas></div>
|
||
</div>
|
||
<div class="chart-box chart-box-half">
|
||
<div class="chart-title">Memória használat (MB)</div>
|
||
<div class="chart-wrap chart-wrap-bar"><canvas id="chart-container-mem"></canvas></div>
|
||
</div>
|
||
</div>
|
||
<div class="chart-empty" id="container-charts-empty" style="display:none">
|
||
Még nincsenek konténer adatok.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 5: Per-container detail (expandable) -->
|
||
<div class="monitor-card" id="container-detail-panel" style="display:none">
|
||
<div class="monitor-card-header">
|
||
<h3 id="container-detail-title">–</h3>
|
||
<div class="time-range-bar" id="container-range-bar">
|
||
<button class="filter-btn active" data-range="1h">1 óra</button>
|
||
<button class="filter-btn" data-range="6h">6 óra</button>
|
||
<button class="filter-btn" data-range="24h">24 óra</button>
|
||
<button class="filter-btn" data-range="7d">7 nap</button>
|
||
</div>
|
||
<button class="btn btn-outline btn-sm" onclick="closeContainerDetail()">Bezárás</button>
|
||
</div>
|
||
<div class="charts-grid charts-grid-2">
|
||
<div class="chart-box">
|
||
<div class="chart-title">CPU %</div>
|
||
<div class="chart-wrap"><canvas id="chart-detail-cpu"></canvas></div>
|
||
</div>
|
||
<div class="chart-box">
|
||
<div class="chart-title">Memória (MB)</div>
|
||
<div class="chart-wrap"><canvas id="chart-detail-mem"></canvas></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/static/chart.min.js"></script>
|
||
<script>
|
||
(function() {
|
||
'use strict';
|
||
|
||
// --- Chart.js dark theme defaults ---
|
||
var colors = {
|
||
cpu: {border: '#0088cc', bg: 'rgba(0,136,204,0.1)'},
|
||
memory: {border: '#238636', bg: 'rgba(35,134,54,0.1)'},
|
||
temp: {border: '#d29922', bg: 'rgba(210,153,34,0.1)'},
|
||
load: {border: '#db6d28', bg: 'rgba(219,109,40,0.1)'}
|
||
};
|
||
|
||
// --- 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) {
|
||
var now = Date.now();
|
||
var defaultRangeMs = parseRangeMs('1h');
|
||
return {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
animation: {duration: 300},
|
||
plugins: {
|
||
legend: {display: false},
|
||
tooltip: {
|
||
backgroundColor: '#1c2128',
|
||
titleColor: '#e6edf3',
|
||
bodyColor: '#8b949e',
|
||
borderColor: '#30363d',
|
||
borderWidth: 1,
|
||
callbacks: {
|
||
title: function(items) {
|
||
if (!items.length) return '';
|
||
var raw = items[0].raw;
|
||
if (raw && typeof raw === 'object' && raw.x) {
|
||
return formatTimestamp(raw.x);
|
||
}
|
||
if (items[0].parsed && items[0].parsed.x) {
|
||
return formatTimestamp(items[0].parsed.x);
|
||
}
|
||
return '';
|
||
}
|
||
}
|
||
}
|
||
},
|
||
scales: {
|
||
x: {
|
||
type: 'linear',
|
||
min: now - defaultRangeMs,
|
||
max: now,
|
||
grid: {color: 'rgba(48,54,61,0.5)'},
|
||
ticks: {
|
||
color: '#8b949e',
|
||
maxTicksLimit: 8,
|
||
callback: function(v) {
|
||
return formatTimeLabel(v);
|
||
}
|
||
}
|
||
},
|
||
y: {
|
||
grid: {color: 'rgba(48,54,61,0.5)'},
|
||
ticks: {color: '#8b949e'},
|
||
beginAtZero: beginAtZero !== false,
|
||
title: {display: !!yLabel, text: yLabel || '', color: '#6e7681', font: {size: 11}}
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
var lineDataset = function(color) {
|
||
return {
|
||
borderColor: color.border,
|
||
backgroundColor: color.bg,
|
||
borderWidth: 2,
|
||
pointRadius: 0,
|
||
pointHitRadius: 10,
|
||
tension: 0.3,
|
||
fill: true,
|
||
spanGaps: true
|
||
};
|
||
};
|
||
|
||
// --- Timezone formatting ---
|
||
var budaTZ = 'Europe/Budapest';
|
||
|
||
// Current range for choosing date vs time format in tick labels
|
||
var currentTickFormat = 'time';
|
||
|
||
function formatTimestamp(ts) {
|
||
if (ts === null || ts === undefined || ts === '') return '';
|
||
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'});
|
||
}
|
||
|
||
function formatTimeLabel(ts) {
|
||
if (ts === null || ts === undefined || ts === '') return '';
|
||
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'});
|
||
}
|
||
|
||
// --- Shared: build {x, y} point array from timestamps + values ---
|
||
function buildXYData(timestamps, values) {
|
||
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() {
|
||
var mkChart = function(id, color, yLabel, beginAtZero) {
|
||
return new Chart(document.getElementById(id), {
|
||
type: 'line',
|
||
data: {datasets: [{data: [], ...lineDataset(color)}]},
|
||
options: chartOpts(yLabel, beginAtZero)
|
||
});
|
||
};
|
||
chartCPU = mkChart('chart-cpu', colors.cpu, '%', true);
|
||
chartMem = mkChart('chart-memory', colors.memory, 'GB', true);
|
||
chartTemp = mkChart('chart-temp', colors.temp, '°C', false);
|
||
chartLoad = mkChart('chart-load', colors.load, '', true);
|
||
}
|
||
|
||
async function loadSystemMetrics() {
|
||
try {
|
||
var resp = await fetch('/api/metrics/system?range=' + systemRange + '&resolution=200');
|
||
var json = await resp.json();
|
||
if (!json.ok || !json.data) return;
|
||
var d = json.data;
|
||
|
||
if (!d.labels || d.labels.length === 0) {
|
||
document.getElementById('system-charts').style.display = 'none';
|
||
document.getElementById('system-charts-empty').style.display = 'block';
|
||
return;
|
||
}
|
||
document.getElementById('system-charts').style.display = '';
|
||
document.getElementById('system-charts-empty').style.display = 'none';
|
||
|
||
// Convert Unix seconds to milliseconds
|
||
var timestamps = d.labels.map(function(ts) { return ts * 1000; });
|
||
|
||
// Update tick label format based on range
|
||
currentTickFormat = tickFormatForRange(systemRange);
|
||
|
||
// Set x-axis bounds to the full requested range
|
||
var allCharts = [chartCPU, chartMem, chartTemp, chartLoad];
|
||
allCharts.forEach(function(c) { setChartXBounds(c, systemRange); });
|
||
|
||
// 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) {
|
||
console.error('Failed to load system metrics:', e);
|
||
}
|
||
}
|
||
|
||
// Range bar clicks
|
||
document.getElementById('system-range-bar').addEventListener('click', function(e) {
|
||
var btn = e.target.closest('.filter-btn');
|
||
if (!btn) return;
|
||
this.querySelectorAll('.filter-btn').forEach(function(b) { b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
systemRange = btn.dataset.range;
|
||
loadSystemMetrics();
|
||
});
|
||
|
||
// =============================================
|
||
// CONTAINER BAR CHARTS
|
||
// =============================================
|
||
var chartContainerCPU, chartContainerMem;
|
||
var containerNames = [];
|
||
|
||
function initContainerCharts() {
|
||
var barOpts = function(xLabel) {
|
||
return {
|
||
indexAxis: 'y',
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
animation: {duration: 300},
|
||
plugins: {
|
||
legend: {display: false},
|
||
tooltip: {
|
||
backgroundColor: '#1c2128',
|
||
titleColor: '#e6edf3',
|
||
bodyColor: '#8b949e',
|
||
borderColor: '#30363d',
|
||
borderWidth: 1
|
||
}
|
||
},
|
||
scales: {
|
||
x: {
|
||
grid: {color: 'rgba(48,54,61,0.5)'},
|
||
ticks: {color: '#8b949e'},
|
||
beginAtZero: true,
|
||
title: {display: true, text: xLabel, color: '#6e7681', font: {size: 11}}
|
||
},
|
||
y: {
|
||
grid: {display: false},
|
||
ticks: {color: '#8b949e', font: {size: 12}}
|
||
}
|
||
},
|
||
onClick: function(evt, elements) {
|
||
if (elements.length > 0) {
|
||
var idx = elements[0].index;
|
||
if (containerNames[idx]) {
|
||
showContainerDetail(containerNames[idx]);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
};
|
||
|
||
chartContainerCPU = new Chart(document.getElementById('chart-container-cpu'), {
|
||
type: 'bar',
|
||
data: {labels: [], datasets: [{data: [], backgroundColor: '#0088cc', borderRadius: 4}]},
|
||
options: barOpts('%')
|
||
});
|
||
chartContainerMem = new Chart(document.getElementById('chart-container-mem'), {
|
||
type: 'bar',
|
||
data: {labels: [], datasets: [{data: [], backgroundColor: '#238636', borderRadius: 4}]},
|
||
options: barOpts('MB')
|
||
});
|
||
}
|
||
|
||
async function loadContainerSummary() {
|
||
try {
|
||
var resp = await fetch('/api/metrics/containers/summary');
|
||
var json = await resp.json();
|
||
if (!json.ok || !json.data) return;
|
||
|
||
var data = json.data;
|
||
if (!data.length) {
|
||
document.getElementById('container-charts').style.display = 'none';
|
||
document.getElementById('container-charts-empty').style.display = 'block';
|
||
return;
|
||
}
|
||
document.getElementById('container-charts').style.display = '';
|
||
document.getElementById('container-charts-empty').style.display = 'none';
|
||
|
||
containerNames = data.map(function(c) { return c.name; });
|
||
var cpuData = data.map(function(c) { return Math.round(c.cpu_percent * 100) / 100; });
|
||
var memData = data.map(function(c) { return Math.round(c.mem_usage_mb); });
|
||
|
||
// Adjust bar chart height based on container count
|
||
var h = Math.max(200, data.length * 35 + 60);
|
||
document.querySelectorAll('.chart-wrap-bar').forEach(function(el) { el.style.height = h + 'px'; });
|
||
|
||
chartContainerCPU.data.labels = containerNames;
|
||
chartContainerCPU.data.datasets[0].data = cpuData;
|
||
chartContainerCPU.update('none');
|
||
|
||
chartContainerMem.data.labels = containerNames;
|
||
chartContainerMem.data.datasets[0].data = memData;
|
||
chartContainerMem.update('none');
|
||
} catch(e) {
|
||
console.error('Failed to load container summary:', e);
|
||
}
|
||
}
|
||
|
||
// =============================================
|
||
// CONTAINER DETAIL (per-container history)
|
||
// =============================================
|
||
var detailChartCPU, detailChartMem;
|
||
var detailContainer = '';
|
||
var detailRange = '1h';
|
||
|
||
function initDetailCharts() {
|
||
var mkChart = function(id, color, yLabel) {
|
||
return new Chart(document.getElementById(id), {
|
||
type: 'line',
|
||
data: {datasets: [{data: [], ...lineDataset(color)}]},
|
||
options: chartOpts(yLabel, true)
|
||
});
|
||
};
|
||
detailChartCPU = mkChart('chart-detail-cpu', colors.cpu, '%');
|
||
detailChartMem = mkChart('chart-detail-mem', colors.memory, 'MB');
|
||
}
|
||
|
||
window.showContainerDetail = async function(name) {
|
||
detailContainer = name;
|
||
document.getElementById('container-detail-title').textContent = name + ' — Erőforrás előzmények';
|
||
document.getElementById('container-detail-panel').style.display = '';
|
||
document.getElementById('container-detail-panel').scrollIntoView({behavior: 'smooth'});
|
||
await loadContainerDetail();
|
||
};
|
||
|
||
window.closeContainerDetail = function() {
|
||
document.getElementById('container-detail-panel').style.display = 'none';
|
||
detailContainer = '';
|
||
};
|
||
|
||
async function loadContainerDetail() {
|
||
if (!detailContainer) return;
|
||
try {
|
||
var resp = await fetch('/api/metrics/containers/' + encodeURIComponent(detailContainer) + '?range=' + detailRange + '&resolution=150');
|
||
var json = await resp.json();
|
||
if (!json.ok || !json.data) return;
|
||
var d = json.data;
|
||
|
||
// Convert Unix seconds to milliseconds
|
||
var timestamps = (d.labels || []).map(function(ts) { return ts * 1000; });
|
||
|
||
// Set x-axis bounds
|
||
setChartXBounds(detailChartCPU, detailRange);
|
||
setChartXBounds(detailChartMem, detailRange);
|
||
|
||
// Update with {x, y} data
|
||
updateLineChart(detailChartCPU, timestamps, d.cpu || []);
|
||
updateLineChart(detailChartMem, timestamps, d.memory || []);
|
||
} catch(e) {
|
||
console.error('Failed to load container detail:', e);
|
||
}
|
||
}
|
||
|
||
document.getElementById('container-range-bar').addEventListener('click', function(e) {
|
||
var btn = e.target.closest('.filter-btn');
|
||
if (!btn) return;
|
||
this.querySelectorAll('.filter-btn').forEach(function(b) { b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
detailRange = btn.dataset.range;
|
||
loadContainerDetail();
|
||
});
|
||
|
||
// =============================================
|
||
// STATIC SYSTEM INFO
|
||
// =============================================
|
||
async function loadSysInfo() {
|
||
try {
|
||
var resp = await fetch('/api/metrics/sysinfo');
|
||
var json = await resp.json();
|
||
if (!json.ok || !json.data) return;
|
||
var d = json.data;
|
||
|
||
document.getElementById('sysinfo-hostname').textContent = d.hostname || '–';
|
||
document.getElementById('sysinfo-os').textContent = d.os || '–';
|
||
document.getElementById('sysinfo-kernel').textContent = d.kernel || '–';
|
||
|
||
var cpuText = d.cpu_model || '–';
|
||
if (d.cpu_cores > 0) cpuText += ' (' + d.cpu_cores + ' mag)';
|
||
document.getElementById('sysinfo-cpu').textContent = cpuText;
|
||
|
||
if (d.uptime_seconds > 0) {
|
||
document.getElementById('sysinfo-uptime').textContent = formatUptime(d.uptime_seconds);
|
||
}
|
||
if (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'});
|
||
}
|
||
} catch(e) {
|
||
console.error('Failed to load sysinfo:', e);
|
||
}
|
||
}
|
||
|
||
function formatUptime(seconds) {
|
||
var days = Math.floor(seconds / 86400);
|
||
var hours = Math.floor((seconds % 86400) / 3600);
|
||
var minutes = Math.floor((seconds % 3600) / 60);
|
||
if (days > 0) return days + ' nap, ' + hours + ' óra';
|
||
if (hours > 0) return hours + ' óra, ' + minutes + ' perc';
|
||
return minutes + ' perc';
|
||
}
|
||
|
||
// =============================================
|
||
// INIT
|
||
// =============================================
|
||
initSystemCharts();
|
||
initContainerCharts();
|
||
initDetailCharts();
|
||
loadSysInfo();
|
||
loadSystemMetrics();
|
||
loadContainerSummary();
|
||
|
||
// Auto-refresh every 60 seconds
|
||
setInterval(function() {
|
||
loadSystemMetrics();
|
||
loadContainerSummary();
|
||
if (detailContainer) loadContainerDetail();
|
||
}, 60000);
|
||
|
||
})();
|
||
</script>
|
||
|
||
{{template "layout_end" .}}
|
||
{{end}} |