From d53860a9c373f56cc19f987443cca05d547ccf81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Fri, 17 Oct 2025 16:27:03 +0200 Subject: [PATCH] vm management --- app.py | 4 ++-- static/main.js | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index 1dbd1db..0afe3da 100644 --- a/app.py +++ b/app.py @@ -21,11 +21,11 @@ app = Flask(__name__, template_folder="templates", static_folder="static") def run(cmd: List[str], timeout: int = 25) -> subprocess.CompletedProcess: return subprocess.run(cmd, check=False, text=True, capture_output=True, timeout=timeout) -def get_text(cmd: List[str]) -> str: +def get_text(cmd: List[str], timeout: Optional[int] = None) -> str: r = run(cmd, timeout=timeout or 25) return r.stdout if r.returncode == 0 else "" -def get_json(cmd: List[str]) -> Any: +def get_json(cmd: List[str], timeout: Optional[int] = None) -> Any: if cmd and cmd[0] == "pvesh" and "--output-format" not in cmd: cmd = cmd + ["--output-format", "json"] r = run(cmd, timeout=timeout or 25) diff --git a/static/main.js b/static/main.js index 0ac7dd7..20c803e 100644 --- a/static/main.js +++ b/static/main.js @@ -694,7 +694,7 @@ async function renderVMAdmin() { const sel = ``; - const migrateBtn = ``; + const migrateBtn = `
`; return rowHTML([sid, type.toUpperCase(), name, node, st, actions, sel, migrateBtn], `data-sid="${sid}"`); }); @@ -743,6 +743,8 @@ async function renderVMAdmin() { log.textContent += (log.textContent ? '\n' : '') + (ok ? 'Migration finished successfully.' : ('Migration failed: ' + (exit || 'unknown error'))); setRowBusy(tr, false); await doRefresh(); + // auto-collapse on success after 2s + if (ok) { setTimeout(() => { const row = tr.nextElementSibling; if (row && row.classList.contains('mig-row')) setMigRowVisible(row, false); }, 2000); } }); } else { await vmAction(sid, action, needsTarget ? getTarget() : undefined); @@ -762,6 +764,13 @@ async function renderVMAdmin() { bind('.act-stop', 'stop'); bind('.act-shutdown', 'shutdown'); bind('.act-migrate', 'migrate', true); + const statusBtn = tr.querySelector('.act-status'); + if (statusBtn) { + statusBtn.onclick = () => { + const row = ensureMigRow(tr, colSpan); + setMigRowVisible(row, row.classList.contains('d-none')); + }; + } }); } @@ -860,6 +869,7 @@ async function tailTaskLog(upid, node, preEl, stopSignal) { const append = (txt) => { if (!preEl) return; preEl.textContent = (preEl.textContent ? preEl.textContent + '\n' : '') + txt; + trimLogEl(preEl); preEl.scrollTop = preEl.scrollHeight; }; while (!stopSignal.done) { @@ -878,4 +888,14 @@ async function tailTaskLog(upid, node, preEl, stopSignal) { } await delay(1500); } +} + + +const LOG_MAX_CHARS = 64 * 1024; // 64KB cap for log panel +function trimLogEl(preEl) { + if (!preEl) return; + const txt = preEl.textContent || ''; + if (txt.length > LOG_MAX_CHARS) { + preEl.textContent = txt.slice(-LOG_MAX_CHARS); + } } \ No newline at end of file