v0.143.0 — guest RAM resize UI (R-24) — MinAgent: 0.90.0

The customer sees the guest's current memory + allowed range on the Rendszer settings
page and resizes it. The controller proxies + maps the agent's machine code to Hungarian;
the agent (felhom-agent v0.90.0) enforces every bound and applies live (no reboot).

agentapi: GuestMemory + ResizeMemory; ruled 412 -> *MemoryRefusedError (code+bounds);
pre-0.90 agent 404 -> typed *StatusError. Capability: FeatureGuestMemoryResize +
featureMinAgent 0.90.0 + a featureProbes row (type-asserts GuestMemory so the shared
SupportProber/netAgent are untouched).

UI (internal/web/system_memory_handlers.go, settings_system.html): "Szerver memoria (RAM)"
card + number input; POST /api/system/memory/resize -> capability gate -> agent -> flash.
JS confirm only on shrink. Code->Hungarian map; agent-outdated hides the control;
agent-unreachable falls back to the guest's /proc/meminfo. Agent English never shown raw.

Tests: agentapi (decode, 404, refusal-code, capability table) + web handler (success/
below_usage_floor/agent_outdated). Gates + go build/vet/test all pass.
This commit is contained in:
2026-07-17 19:10:00 +02:00
parent f900c83eed
commit 3286c7faaf
10 changed files with 688 additions and 1 deletions
@@ -143,6 +143,48 @@
</div>
</div>
<div class="settings-card">
<h3>Szerver memória (RAM)</h3>
{{if .MemorySupported}}
{{if .MemoryReachable}}
<div class="settings-grid">
<div class="settings-row">
<span class="settings-label">Jelenlegi memória</span>
<span class="settings-value mono">{{.MemoryAllocatedMB}} MB</span>
</div>
<div class="settings-row">
<span class="settings-label">Felhasznált</span>
<span class="settings-value mono">{{.MemoryUsageMB}} MB</span>
</div>
<div class="settings-row">
<span class="settings-label">Megengedett tartomány</span>
<span class="settings-value mono">{{.MemoryMinMB}} MB {{.MemoryMaxMB}} MB</span>
</div>
<div class="settings-row" style="padding-top: 0.5em;">
<span class="settings-label">Új méret (MB)</span>
<span class="settings-value">
<input type="number" id="mem-input" class="input mono" style="width:8em;"
value="{{.MemoryAllocatedMB}}" min="{{.MemoryMinMB}}" max="{{.MemoryMaxMB}}" step="256"
data-current="{{.MemoryAllocatedMB}}">
<button class="btn btn-primary btn-sm" id="btn-mem-resize" onclick="resizeMemory()" style="margin-left:0.5em;">Átméretezés</button>
<span id="mem-status-msg" style="margin-left:0.5em; display:none;"></span>
</span>
</div>
</div>
{{else}}
<div class="settings-grid">
<div class="settings-row">
<span class="settings-label">Jelenlegi memória</span>
<span class="settings-value mono">{{if .MemoryProcMB}}{{.MemoryProcMB}} MB{{else}}n/a{{end}}</span>
</div>
</div>
<p class="settings-card-desc">A kiszolgáló ügynök jelenleg nem elérhető — próbáld meg később.</p>
{{end}}
{{else}}
<p class="settings-card-desc">A memória átméretezéséhez a kiszolgáló rendszerfrissítése szükséges.</p>
{{end}}
</div>
<div id="dialog-root"></div>
<script>
// Light overlay dialog (D1) — replaces the native blocking browser dialogs (same texts).
@@ -220,6 +262,55 @@ function doTriggerUpdate() {
});
}
function resizeMemory() {
var input = document.getElementById('mem-input');
var target = parseInt(input.value, 10);
var current = parseInt(input.getAttribute('data-current'), 10);
if (isNaN(target) || target <= 0) {
showMemMsg('Érvénytelen memória érték', true);
return;
}
if (target < current) {
openDialog({title:'Memória csökkentése', confirmLabel:'Csökkentés',
message:'A memória csökkentése hatással lehet a futó alkalmazások teljesítményére. Biztosan folytatod?',
onConfirm:function(){ doResizeMemory(target); }});
return;
}
doResizeMemory(target);
}
function doResizeMemory(target) {
var btn = document.getElementById('btn-mem-resize');
btn.disabled = true;
btn.textContent = 'Átméretezés...';
fetch('/api/system/memory/resize', {
method:'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
body: JSON.stringify({memory_mb: target})
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.ok) {
showMemMsg(data.error || data.message || 'Kész.', false);
setTimeout(function(){ location.reload(); }, 1200);
} else {
showMemMsg(data.error || 'A művelet nem sikerült', true);
btn.disabled = false;
btn.textContent = 'Átméretezés';
}
})
.catch(function() {
showMemMsg('Kapcsolódási hiba', true);
btn.disabled = false;
btn.textContent = 'Átméretezés';
});
}
function showMemMsg(text, isErr) {
var msg = document.getElementById('mem-status-msg');
if (!msg) return;
msg.textContent = text;
msg.style.color = isErr ? 'var(--danger, #c0392b)' : 'var(--text-2)';
msg.style.display = 'inline';
}
function pollUntilBack() {
var iv = setInterval(function() {
fetch('/api/health')