vm management
This commit is contained in:
4
app.py
4
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)
|
||||
|
@@ -694,7 +694,7 @@ async function renderVMAdmin() {
|
||||
const sel = `<select class="form-select form-select-sm target-node" style="min-width:140px">
|
||||
${nodes.map(n => `<option value="${n}" ${n === x.node ? 'disabled' : ''}>${n}${n === x.node ? ' (src)' : ''}</option>`).join('')}
|
||||
</select>`;
|
||||
const migrateBtn = `<button class="btn btn-outline-primary btn-sm act-migrate">Migrate (offline)</button>`;
|
||||
const migrateBtn = `<div class="d-flex align-items-center gap-2"><button class="btn btn-outline-primary btn-sm act-migrate">Migrate (offline)</button><button class="btn btn-outline-secondary btn-sm act-status">Status</button></div>`;
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user