{% extends "base.html" %}
{% block title %}Monitoring{% endblock %}
{% block breadcrumb %}Monitoring{% endblock %}
{% block extra_head %}
{% endblock %}
{% block content %}
{# ── Alert Strip — dynamic threshold alerts ── #}
{{ monitoring.cpu.percent|default(0)|round(1) }}%
CPU Usage
{{ monitoring.cpu.cores_logical|default(0) }} cores
{{ monitoring.memory.percent|default(0)|round(1) }}%
Memory
{{ monitoring.memory.used_human|default('—') }} / {{ monitoring.memory.total_human|default('—') }}
{{ monitoring.disk.percent|default(0)|round(1) }}%
Disk
{{ monitoring.disk.used_human|default('—') }} / {{ monitoring.disk.total_human|default('—') }}
{{ monitoring.process.threads|default(0) }}
Threads
PID {{ monitoring.process.pid|default('—') }}
Process RSS
{{ monitoring.process.mem_percent|default(0)|round(2) }}% of system
{{ monitoring.python.gc_objects|default(0) }}
GC Objects
{{ monitoring.python.gc_gen0|default(0) }} gen0 collections
{{ monitoring.process.uptime_human|default('—') }}
Uptime
{{ monitoring.process.status|default('—') }}
{# ── Tabs ── #}
Live Metrics 60-second rolling window
{# ── 2-column live charts ── #}
CPU History
{{ monitoring.cpu.percent|default(0)|round(1) }}%
Memory History
{{ monitoring.memory.percent|default(0)|round(1) }}%
{# ── 4-column gauge row ── #}
Resource Gauges
{# ── System Information ── #}
System Information
Platform
| OS | {{ monitoring.system.os|default('—') }} |
| Platform | {{ monitoring.system.platform|default('—') }} |
| Architecture | {{ monitoring.system.arch|default('—') }} |
| Hostname | {{ monitoring.system.hostname|default('—') }} |
| CPU Cores | {{ monitoring.cpu.cores_physical|default('—') }}P / {{ monitoring.cpu.cores_logical|default('—') }}L |
Python Runtime
| Version | {{ monitoring.python.version|default('—') }} |
| Implementation | {{ monitoring.python.implementation|default('—') }} |
| PID | {{ monitoring.process.pid|default('—') }} |
| GC Objects | {{ monitoring.python.gc_objects|default(0)|int }} |
| Executable | {{ monitoring.python.executable|default('—') }} |
Load Average
| 1 min | {{ monitoring.cpu.load_avg_1|default('—') }} |
| 5 min | {{ monitoring.cpu.load_avg_5|default('—') }} |
| 15 min | {{ monitoring.cpu.load_avg_15|default('—') }} |
| CPU Freq | {{ monitoring.cpu.freq_current|default('—') }} MHz |
| Ctx Switches | {{ monitoring.process.ctx_switches|default('—') }} |
{# ═══════════════════════════════════════════════════════════════════ #}
{# ── TAB 2: CPU — per-core chart & times breakdown ── #}
{# ═══════════════════════════════════════════════════════════════════ #}
Subsystem Health
{% if monitoring.health_checks %}
{% for check in monitoring.health_checks %}
{{ check.name }}
{{ check.latency_ms|default(0)|round(1) }}ms
{{ check.message|default('No details') }}
{{ check.checked_at|default('—') }}
{% endfor %}
{% else %}
No health checks registered yet
Subsystems will appear here once they register with the HealthRegistry.
{% endif %}
Disk Partitions
{% if monitoring.disk.partitions|default([]) %}
| Device |
Mount |
FS |
Total |
Used |
Free |
Use% |
{% for p in monitoring.disk.partitions|default([]) %}
| {{ p.device }} |
{{ p.mountpoint }} |
{{ p.fstype }} |
{{ p.total_human }} |
{{ p.used_human }} |
{{ p.free_human }} |
|
{% endfor %}
{% else %}
No partition data available.
{% endif %}
Prometheus Text Format
Metrics Export
OpenMetrics
Click Refresh to load current Prometheus metrics…
{% endblock %}
{% block extra_js %}
// ═══════════════════════════════════════════════════════════════════
// ── Chart.js Monitoring Dashboard — Live Polling ──
// ═══════════════════════════════════════════════════════════════════
var _monData = {{ monitoring|tojson }};
var _urlPrefix = '{{ url_prefix|default("/admin") }}';
var _pollInterval = null;
var _polling = true;
var MAX_POINTS = 20;
// ── Color palette ──
var C = {
green: '#22c55e', greenFade: 'rgba(34,197,94,0.15)',
blue: '#3b82f6', blueFade: 'rgba(59,130,246,0.15)',
amber: '#f59e0b', amberFade: 'rgba(245,158,11,0.15)',
purple: '#a855f7', purpleFade: 'rgba(168,85,247,0.15)',
cyan: '#06b6d4', cyanFade: 'rgba(6,182,212,0.15)',
rose: '#f43f5e', roseFade: 'rgba(244,63,94,0.15)',
gray: '#64748b', grayFade: 'rgba(100,116,139,0.15)',
teal: '#14b8a6'
};
// ── Theme-aware defaults ──
function isDark() { return document.documentElement.getAttribute('data-theme') === 'dark'; }
function gridColor() { return isDark() ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)'; }
function textColor() { return isDark() ? '#94a3b8' : '#64748b'; }
// ── Chart.js global defaults ──
Chart.defaults.font.family = "'Outfit', sans-serif";
Chart.defaults.font.size = 11;
Chart.defaults.color = textColor();
Chart.defaults.animation.duration = 600;
Chart.defaults.animation.easing = 'easeOutQuart';
Chart.defaults.responsive = true;
Chart.defaults.maintainAspectRatio = false;
Chart.defaults.plugins.legend.display = false;
Chart.defaults.plugins.tooltip.backgroundColor = isDark() ? 'rgba(0,0,0,0.85)' : 'rgba(255,255,255,0.95)';
Chart.defaults.plugins.tooltip.titleColor = isDark() ? '#e4e4e7' : '#18181b';
Chart.defaults.plugins.tooltip.bodyColor = isDark() ? '#a1a1aa' : '#52525b';
Chart.defaults.plugins.tooltip.borderColor = isDark() ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';
Chart.defaults.plugins.tooltip.borderWidth = 1;
Chart.defaults.plugins.tooltip.cornerRadius = 8;
Chart.defaults.plugins.tooltip.padding = 10;
// ── History arrays ──
var _cpuHistory = [_monData.cpu.percent || 0];
var _memHistory = [_monData.memory.percent || 0];
var _netSentKBs = [0];
var _netRecvKBs = [0];
var _gcHistory = [_monData.python.gc_objects || 0];
var _prevNetSent = _monData.network.bytes_sent || 0;
var _prevNetRecv = _monData.network.bytes_recv || 0;
var _timeLabels = ['0'];
// ── Chart instances ──
var charts = {};
function scaleOpts(maxVal, unit) {
return {
x: { display: true, grid: { color: gridColor(), drawBorder: false }, ticks: { color: textColor(), maxTicksLimit: 6, font: { size: 9 } } },
y: { display: true, min: 0, max: maxVal || undefined, grid: { color: gridColor(), drawBorder: false }, ticks: { color: textColor(), callback: function(v) { return v + (unit||''); }, font: { size: 9 } } }
};
}
function lineDataset(label, data, color, fade) {
return {
label: label, data: data,
borderColor: color, backgroundColor: fade,
borderWidth: 2, pointRadius: 0, pointHoverRadius: 4,
fill: true, tension: 0.35
};
}
function initCharts() {
// ── CPU History (line) ──
charts.cpuHistory = new Chart(document.getElementById('chartCpuHistory'), {
type: 'line',
data: { labels: _timeLabels.slice(), datasets: [lineDataset('CPU %', _cpuHistory.slice(), C.green, C.greenFade)] },
options: { scales: scaleOpts(100, '%'), plugins: { tooltip: { callbacks: { label: function(c) { return 'CPU: ' + c.raw.toFixed(1) + '%'; } } } } }
});
// ── Memory History (line) ──
charts.memHistory = new Chart(document.getElementById('chartMemHistory'), {
type: 'line',
data: { labels: _timeLabels.slice(), datasets: [lineDataset('Memory %', _memHistory.slice(), C.blue, C.blueFade)] },
options: { scales: scaleOpts(100, '%'), plugins: { tooltip: { callbacks: { label: function(c) { return 'Mem: ' + c.raw.toFixed(1) + '%'; } } } } }
});
// ── Gauges (doughnut) ──
function gaugeData(pct, color) {
return {
type: 'doughnut',
data: { labels: ['Used', 'Free'], datasets: [{ data: [pct, 100 - pct], backgroundColor: [color, gridColor()], borderWidth: 0, cutout: '72%' }] },
options: {
circumference: 180, rotation: 270,
plugins: {
tooltip: { enabled: false },
legend: { display: false }
},
animation: { animateRotate: true, duration: 800 }
},
plugins: [{
id: 'gaugeCenter',
afterDraw: function(chart) {
var ctx = chart.ctx, w = chart.width, h = chart.height;
ctx.save();
ctx.font = '700 1.3rem Outfit, sans-serif';
ctx.fillStyle = textColor();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(chart.data.datasets[0].data[0].toFixed(1) + '%', w/2, h * 0.65);
ctx.restore();
}
}]
};
}
charts.gaugeCpu = new Chart(document.getElementById('gaugeCpu'), gaugeData(_monData.cpu.percent || 0, C.green));
charts.gaugeMem = new Chart(document.getElementById('gaugeMem'), gaugeData(_monData.memory.percent || 0, C.blue));
charts.gaugeDisk = new Chart(document.getElementById('gaugeDisk'), gaugeData(_monData.disk.percent || 0, C.amber));
charts.gaugeSwap = new Chart(document.getElementById('gaugeSwap'), gaugeData(_monData.memory.swap_percent || 0, C.purple));
// ── CPU Per-Core (bar) ──
var coreLabels = (_monData.cpu.per_core || []).map(function(_, i) { return 'Core ' + i; });
var coreColors = (_monData.cpu.per_core || []).map(function(_, i) { return i % 2 === 0 ? C.green : C.cyan; });
charts.cpuCores = new Chart(document.getElementById('chartCpuCores'), {
type: 'bar',
data: { labels: coreLabels, datasets: [{ label: 'Usage %', data: _monData.cpu.per_core || [], backgroundColor: coreColors, borderRadius: 4, maxBarThickness: 40 }] },
options: { scales: scaleOpts(100, '%'), plugins: { tooltip: { callbacks: { label: function(c) { return c.label + ': ' + c.raw.toFixed(1) + '%'; } } } } }
});
// ── CPU Times (doughnut) ──
charts.cpuTimes = new Chart(document.getElementById('chartCpuTimes'), {
type: 'doughnut',
data: {
labels: ['User', 'System', 'Idle'],
datasets: [{ data: [_monData.cpu.times_user||0, _monData.cpu.times_system||0, _monData.cpu.times_idle||0], backgroundColor: [C.green, C.amber, C.gray], borderWidth: 0, cutout: '55%' }]
},
options: { plugins: { legend: { display: true, position: 'bottom', labels: { padding: 14, usePointStyle: true, pointStyle: 'circle', font: { size: 10 } } } } }
});
// ── Memory Breakdown (doughnut) ──
charts.memBreakdown = new Chart(document.getElementById('chartMemBreakdown'), {
type: 'doughnut',
data: {
labels: ['Used', 'Available'],
datasets: [{ data: [_monData.memory.used||0, _monData.memory.available||0], backgroundColor: [C.blue, C.gray], borderWidth: 0, cutout: '55%' }]
},
options: { plugins: { legend: { display: true, position: 'bottom', labels: { padding: 14, usePointStyle: true, pointStyle: 'circle', font: { size: 10 } } } } }
});
// ── Process Memory (doughnut) ──
var procRss = _monData.process.rss || 0;
var procVms = _monData.process.vms || 0;
charts.procMem = new Chart(document.getElementById('chartProcMem'), {
type: 'doughnut',
data: {
labels: ['RSS', 'VMS (other)'],
datasets: [{ data: [procRss, Math.max(0, procVms - procRss)], backgroundColor: [C.green, C.gray], borderWidth: 0, cutout: '55%' }]
},
options: { plugins: { legend: { display: true, position: 'bottom', labels: { padding: 14, usePointStyle: true, pointStyle: 'circle', font: { size: 10 } } } } }
});
// ── Network Throughput (line) ──
charts.netThroughput = new Chart(document.getElementById('chartNetThroughput'), {
type: 'line',
data: {
labels: _timeLabels.slice(),
datasets: [
lineDataset('Sent KB/s', _netSentKBs.slice(), C.green, C.greenFade),
lineDataset('Recv KB/s', _netRecvKBs.slice(), C.cyan, C.cyanFade)
]
},
options: {
scales: scaleOpts(null, ''),
plugins: { legend: { display: true, position: 'top', labels: { padding: 10, usePointStyle: true, pointStyle: 'circle', font: { size: 10 } } } }
}
});
// ── Network I/O cumulative (bar) ──
charts.netIO = new Chart(document.getElementById('chartNetIO'), {
type: 'bar',
data: {
labels: ['Sent', 'Received'],
datasets: [{ label: 'MB', data: [(_monData.network.bytes_sent||0)/1048576, (_monData.network.bytes_recv||0)/1048576], backgroundColor: [C.green, C.cyan], borderRadius: 6, maxBarThickness: 80 }]
},
options: { indexAxis: 'y', scales: { x: { grid: { color: gridColor() }, ticks: { color: textColor(), callback: function(v) { return v.toFixed(0) + ' MB'; } } }, y: { grid: { display: false }, ticks: { color: textColor() } } } }
});
// ── Process Memory Map (doughnut) ──
charts.procMemMap = new Chart(document.getElementById('chartProcMemMap'), {
type: 'doughnut',
data: {
labels: ['RSS', 'Shared', 'Private'],
datasets: [{ data: [_monData.process.rss||0, _monData.process.shared||0, _monData.process.private||0], backgroundColor: [C.green, C.blue, C.purple], borderWidth: 0, cutout: '55%' }]
},
options: { plugins: { legend: { display: true, position: 'bottom', labels: { padding: 14, usePointStyle: true, pointStyle: 'circle', font: { size: 10 } } } } }
});
// ── GC History (line) ──
charts.gcHistory = new Chart(document.getElementById('chartGCHistory'), {
type: 'line',
data: { labels: _timeLabels.slice(), datasets: [lineDataset('GC Objects', _gcHistory.slice(), C.purple, C.purpleFade)] },
options: { scales: scaleOpts(null, '') }
});
}
// ── Gauge updater ──
function updateGauge(chart, pct, color) {
chart.data.datasets[0].data = [pct, Math.max(0, 100 - pct)];
var effectiveColor = pct < 60 ? color : (pct < 85 ? C.amber : C.rose);
chart.data.datasets[0].backgroundColor = [effectiveColor, gridColor()];
chart.update('none');
}
// ── Alerts ──
function updateAlerts(d) {
var strip = document.getElementById('monAlertStrip');
var alerts = [];
var cpuP = d.cpu.percent || 0;
var memP = d.memory.percent || 0;
var diskP = d.disk.percent || 0;
if (cpuP > 90) alerts.push({ cls: 'crit', msg: '⚠ CPU usage critical: ' + cpuP.toFixed(1) + '%' });
else if (cpuP > 75) alerts.push({ cls: 'warn', msg: '⚡ CPU usage elevated: ' + cpuP.toFixed(1) + '%' });
if (memP > 90) alerts.push({ cls: 'crit', msg: '⚠ Memory usage critical: ' + memP.toFixed(1) + '%' });
else if (memP > 80) alerts.push({ cls: 'warn', msg: '⚡ Memory usage elevated: ' + memP.toFixed(1) + '%' });
if (diskP > 90) alerts.push({ cls: 'crit', msg: '⚠ Disk usage critical: ' + diskP.toFixed(1) + '%' });
else if (diskP > 80) alerts.push({ cls: 'warn', msg: '⚡ Disk usage elevated: ' + diskP.toFixed(1) + '%' });
if (alerts.length === 0) {
alerts.push({ cls: 'ok', msg: '✓ All systems operating within normal parameters' });
}
strip.style.display = 'block';
strip.innerHTML = alerts.map(function(a) { return '