D0 Part 3: deploy + backups + monitoring in v2

- deploy.html: stacked memory bar -> meter track with two segments
  (committed solid blue, new app 35%-opacity blue), neutral threshold
  ticks; integration status badges -> tags; meta pills -> metarows;
  3-step deploy progress icons -> sprite (check/x/triangle-alert/
  spinning rotate-cw); emoji -> icons or plain text incl. the JS
  confirm string.
- backups.html: storage bars -> meters, disconnected drives render an
  empty track with a neutral 'Leválasztva' (no red), drive-state badges
  -> warn tags, checkmark/warning glyphs -> sprite icons, JS flash/info
  strings de-emojified.
- monitoring.html: bars -> meters in both template and JS-generated
  host-storage markup (usageColorClass now emits nominal/warn/crit);
  Chart.js palette per spec (cpu #2EA8F5, memory #8E7CE8, temp #E0A93E,
  load #5EC4B6 — categorical data series keep distinct hues); tooltip/
  grid/tick colors moved to v2 literals; mem-dist palette rebalanced.
- Templates repo-wide: legacy var(--*) names in inline styles and JS
  string literals renamed to v2 tokens (old names no longer resolve).
- style.css: memory-bar family rewritten for the two-segment meter,
  .ico-spin animation added (reduced-motion respected).
This commit is contained in:
2026-07-02 14:32:00 +02:00
parent 5dc277f2b1
commit f100cef842
13 changed files with 178 additions and 196 deletions
@@ -10,7 +10,7 @@
<div class="monitor-card" id="host-health-card">
<div class="monitor-card-header">
<h3>Szerver állapota (gazdagép)</h3>
<span class="sysinfo-value" id="host-health-updated" style="font-size:.75rem;color:var(--text-muted)"></span>
<span class="sysinfo-value" id="host-health-updated" style="font-size:.75rem;color:var(--text-3)"></span>
</div>
<div id="host-health-unavailable" class="monitoring-banner monitoring-banner-yellow" style="display:none">
A gazdagép metrikái jelenleg nem elérhetők.
@@ -79,32 +79,32 @@
<h3>Tárhely</h3>
<div class="storage-bars">
{{with .SystemInfo}}
<div class="storage-item">
<div class="storage-item meter {{usageColor .DiskPercent}}">
<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 class="meter-track">
<div class="meter-fill" style="width:{{printf "%.1f" .DiskPercent}}%"></div>
</div>
</div>
{{range $.StorageBars}}
{{if .Disconnected}}
<div class="storage-item storage-disconnected">
<div class="storage-item meter meter-empty storage-disconnected">
<div class="storage-header">
<span class="storage-label">{{.Label}}</span>
<span class="storage-value badge-error" style="font-size:.75rem">Leválasztva</span>
<span class="storage-value"><span class="state-text-neutral">Leválasztva</span></span>
</div>
<div class="system-bar"><div class="system-bar-disconnected"></div></div>
<div class="meter-track"></div>
</div>
{{else}}
<div class="storage-item">
<div class="storage-item meter {{usageColor .Percent}}">
<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 class="meter-track">
<div class="meter-fill" style="width:{{printf "%.1f" .Percent}}%"></div>
</div>
{{if .Purpose}}<div class="storage-purpose" style="font-size:.72rem;opacity:.65;margin-top:.2rem">{{.Purpose}}</div>{{end}}
</div>
@@ -259,10 +259,10 @@
// --- 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)'}
cpu: {border: '#2EA8F5', bg: 'rgba(46,168,245,0.1)'},
memory: {border: '#8E7CE8', bg: 'rgba(142,124,232,0.1)'},
temp: {border: '#E0A93E', bg: 'rgba(224,169,62,0.1)'},
load: {border: '#5EC4B6', bg: 'rgba(94,196,182,0.1)'}
};
// --- Range helper: returns milliseconds for a range string ---
@@ -294,10 +294,10 @@
plugins: {
legend: {display: false},
tooltip: {
backgroundColor: '#1c2128',
titleColor: '#e6edf3',
bodyColor: '#8b949e',
borderColor: '#30363d',
backgroundColor: '#0F1B2E',
titleColor: '#EDF2F9',
bodyColor: '#94A6BF',
borderColor: '#22344F',
borderWidth: 1,
callbacks: {
title: function(items) {
@@ -319,9 +319,9 @@
type: 'linear',
min: now - defaultRangeMs,
max: now,
grid: {color: 'rgba(48,54,61,0.5)'},
grid: {color: 'rgba(34,52,79,0.5)'},
ticks: {
color: '#8b949e',
color: '#94A6BF',
maxTicksLimit: 8,
callback: function(v) {
return formatTimeLabel(v);
@@ -329,10 +329,10 @@
}
},
y: {
grid: {color: 'rgba(48,54,61,0.5)'},
ticks: {color: '#8b949e'},
grid: {color: 'rgba(34,52,79,0.5)'},
ticks: {color: '#94A6BF'},
beginAtZero: beginAtZero !== false,
title: {display: !!yLabel, text: yLabel || '', color: '#6e7681', font: {size: 11}}
title: {display: !!yLabel, text: yLabel || '', color: '#5E7392', font: {size: 11}}
}
}
};
@@ -481,23 +481,23 @@
plugins: {
legend: {display: false},
tooltip: {
backgroundColor: '#1c2128',
titleColor: '#e6edf3',
bodyColor: '#8b949e',
borderColor: '#30363d',
backgroundColor: '#0F1B2E',
titleColor: '#EDF2F9',
bodyColor: '#94A6BF',
borderColor: '#22344F',
borderWidth: 1
}
},
scales: {
x: {
grid: {color: 'rgba(48,54,61,0.5)'},
ticks: {color: '#8b949e'},
grid: {color: 'rgba(34,52,79,0.5)'},
ticks: {color: '#94A6BF'},
beginAtZero: true,
title: {display: true, text: xLabel, color: '#6e7681', font: {size: 11}}
title: {display: true, text: xLabel, color: '#5E7392', font: {size: 11}}
},
y: {
grid: {display: false},
ticks: {color: '#8b949e', font: {size: 12}}
ticks: {color: '#94A6BF', font: {size: 12}}
}
},
onClick: function(evt, elements) {
@@ -513,12 +513,12 @@
chartContainerCPU = new Chart(document.getElementById('chart-container-cpu'), {
type: 'bar',
data: {labels: [], datasets: [{data: [], backgroundColor: '#0088cc', borderRadius: 4}]},
data: {labels: [], datasets: [{data: [], backgroundColor: '#2EA8F5', borderRadius: 2}]},
options: barOpts('%')
});
chartContainerMem = new Chart(document.getElementById('chart-container-mem'), {
type: 'bar',
data: {labels: [], datasets: [{data: [], backgroundColor: '#238636', borderRadius: 4}]},
data: {labels: [], datasets: [{data: [], backgroundColor: '#8E7CE8', borderRadius: 2}]},
options: barOpts('MB')
});
}
@@ -560,7 +560,7 @@
}
}
var memDistPalette = ['#238636','#0088cc','#d29922','#da3633','#8b5cf6','#ec6547','#2ea043','#1f6feb','#e3b341','#f47067'];
var memDistPalette = ['#2EA8F5','#8E7CE8','#5EC4B6','#E0A93E','#D67BB5','#7FA6E8','#59B88C','#E8935E','#6FCADF','#B3A1F0'];
async function buildMemoryDistributionBar(containers) {
var totalMB = {{.SystemInfo.TotalMemMB}};
@@ -608,12 +608,12 @@
// OS / system overhead
if (osMB > 10) {
var osPct = (osMB / totalMB * 100).toFixed(2);
html += '<div class="memory-bar-segment" style="width:' + osPct + '%;background:var(--text-muted);opacity:0.5" title="Rendszer: ' + fmtMB(osMB) + '"></div>';
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--text-muted);opacity:0.5"></span>Rendszer (' + fmtMB(osMB) + ')</div>';
html += '<div class="memory-bar-segment" style="width:' + osPct + '%;background:var(--text-3);opacity:0.5" title="Rendszer: ' + fmtMB(osMB) + '"></div>';
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--text-3);opacity:0.5"></span>Rendszer (' + fmtMB(osMB) + ')</div>';
}
// Free space legend only
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--bg-secondary);border:1px solid var(--border-color)"></span>Szabad (' + fmtMB(freeMB) + ')</div>';
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--bg-2);border:1px solid var(--line)"></span>Szabad (' + fmtMB(freeMB) + ')</div>';
bar.innerHTML = html;
legend.innerHTML = legendHtml;
@@ -688,9 +688,9 @@
// HOST (PROXMOX BOX) HEALTH — slice 9, proxied from the host agent
// =============================================
function usageColorClass(pct) {
if (pct >= 85) return 'system-bar-red';
if (pct >= 70) return 'system-bar-yellow';
return 'system-bar-green';
if (pct >= 85) return 'crit';
if (pct >= 70) return 'warn';
return 'nominal';
}
function fmtBytesGB(bytes) {
@@ -745,21 +745,21 @@
var bars = document.getElementById('host-storage-bars');
var targets = Array.isArray(d.storage_targets) ? d.storage_targets : [];
if (!targets.length) {
bars.innerHTML = '<div class="sysinfo-value" style="color:var(--text-muted)">Nincs tároló adat.</div>';
bars.innerHTML = '<div class="sysinfo-value" style="color:var(--text-3)">Nincs tároló adat.</div>';
} else {
var html = '';
targets.forEach(function(t) {
// Friendly label (server-supplied) with the raw PVE storage id shown muted for clarity.
var friendly = escapeHtml(t.label || t.name || '');
var rawId = escapeHtml(t.name || '');
var idHtml = rawId ? ' <span style="color:var(--text-muted);font-size:.72rem">(' + rawId + ')</span>' : '';
var idHtml = rawId ? ' <span style="color:var(--text-3);font-size:.72rem">(' + rawId + ')</span>' : '';
var label = friendly + idHtml;
var purposeHtml = t.purpose ? '<div class="storage-purpose" style="font-size:.72rem;opacity:.65;margin-top:.2rem">' + escapeHtml(t.purpose) + '</div>' : '';
if (t.state && t.state !== 'attached') {
html += '<div class="storage-item storage-disconnected">' +
html += '<div class="storage-item meter meter-empty storage-disconnected">' +
'<div class="storage-header"><span class="storage-label">' + label + '</span>' +
'<span class="storage-value badge-error" style="font-size:.75rem">Nem elérhető</span></div>' +
'<div class="system-bar"><div class="system-bar-disconnected"></div></div>' + purposeHtml + '</div>';
'<span class="storage-value">— <span class="state-text-neutral">Nem elérhető</span></span></div>' +
'<div class="meter-track"></div>' + purposeHtml + '</div>';
return;
}
var pct = (t.used_fraction != null ? t.used_fraction * 100 : 0);
@@ -772,13 +772,13 @@
if (t.smart.temperature_c != null) extra.push(t.smart.temperature_c + ' °C');
if (t.smart.percentage_used != null) extra.push('kopás: ' + t.smart.percentage_used + '%');
}
var extraHtml = extra.length ? ' <span style="color:var(--text-muted);font-size:.75rem">· ' + escapeHtml(extra.join(' · ')) + '</span>' : '';
html += '<div class="storage-item">' +
var extraHtml = extra.length ? ' <span style="color:var(--text-3);font-size:.75rem">· ' + escapeHtml(extra.join(' · ')) + '</span>' : '';
html += '<div class="storage-item meter ' + usageColorClass(pct) + '">' +
'<div class="storage-header">' +
'<span class="storage-label">' + label + extraHtml + '</span>' +
'<span class="storage-value">' + fmtBytesGB(t.used_bytes) + ' / ' + fmtBytesGB(t.total_bytes) +
' (' + Math.round(pct) + '%)</span></div>' +
'<div class="system-bar"><div class="system-bar-fill ' + usageColorClass(pct) +
'<div class="meter-track"><div class="meter-fill' +
'" style="width:' + Math.min(100, pct).toFixed(1) + '%"></div></div>' + purposeHtml + '</div>';
});
bars.innerHTML = html;