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:
|
def run(cmd: List[str], timeout: int = 25) -> subprocess.CompletedProcess:
|
||||||
return subprocess.run(cmd, check=False, text=True, capture_output=True, timeout=timeout)
|
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)
|
r = run(cmd, timeout=timeout or 25)
|
||||||
return r.stdout if r.returncode == 0 else ""
|
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:
|
if cmd and cmd[0] == "pvesh" and "--output-format" not in cmd:
|
||||||
cmd = cmd + ["--output-format", "json"]
|
cmd = cmd + ["--output-format", "json"]
|
||||||
r = run(cmd, timeout=timeout or 25)
|
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">
|
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('')}
|
${nodes.map(n => `<option value="${n}" ${n === x.node ? 'disabled' : ''}>${n}${n === x.node ? ' (src)' : ''}</option>`).join('')}
|
||||||
</select>`;
|
</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}"`);
|
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')));
|
log.textContent += (log.textContent ? '\n' : '') + (ok ? 'Migration finished successfully.' : ('Migration failed: ' + (exit || 'unknown error')));
|
||||||
setRowBusy(tr, false);
|
setRowBusy(tr, false);
|
||||||
await doRefresh();
|
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 {
|
} else {
|
||||||
await vmAction(sid, action, needsTarget ? getTarget() : undefined);
|
await vmAction(sid, action, needsTarget ? getTarget() : undefined);
|
||||||
@@ -762,6 +764,13 @@ async function renderVMAdmin() {
|
|||||||
bind('.act-stop', 'stop');
|
bind('.act-stop', 'stop');
|
||||||
bind('.act-shutdown', 'shutdown');
|
bind('.act-shutdown', 'shutdown');
|
||||||
bind('.act-migrate', 'migrate', true);
|
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) => {
|
const append = (txt) => {
|
||||||
if (!preEl) return;
|
if (!preEl) return;
|
||||||
preEl.textContent = (preEl.textContent ? preEl.textContent + '\n' : '') + txt;
|
preEl.textContent = (preEl.textContent ? preEl.textContent + '\n' : '') + txt;
|
||||||
|
trimLogEl(preEl);
|
||||||
preEl.scrollTop = preEl.scrollHeight;
|
preEl.scrollTop = preEl.scrollHeight;
|
||||||
};
|
};
|
||||||
while (!stopSignal.done) {
|
while (!stopSignal.done) {
|
||||||
@@ -879,3 +889,13 @@ async function tailTaskLog(upid, node, preEl, stopSignal) {
|
|||||||
await delay(1500);
|
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