125 lines
4 KiB
HTML
125 lines
4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Converting VM — VM Bench</title>
|
|
<link rel="stylesheet" href="/static/proxmox.css">
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div class="logo">VM Bench</div>
|
|
<div class="sub">Proxmox Image Conversion</div>
|
|
</header>
|
|
|
|
<div class="container">
|
|
<div class="panel poll-panel">
|
|
<h2>Converting VM {{ vmid }}</h2>
|
|
<p class="dim">Job: <code>{{ job_id }}</code> | {{ vm_name }}</p>
|
|
|
|
<div id="status-badge" class="badge badge-queued">
|
|
<span class="spinner"></span> Queued
|
|
</div>
|
|
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="progress-fill" style="width:0%">0%</div>
|
|
</div>
|
|
|
|
<p id="status-message" class="dim">Waiting for backend...</p>
|
|
<p id="elapsed" class="dim">Elapsed: 0s</p>
|
|
<div id="error-block" class="error hidden"></div>
|
|
</div>
|
|
|
|
<!-- Reuse section (shown after completion) -->
|
|
<div id="reuse-section" class="panel hidden">
|
|
<h2>Conversion Complete</h2>
|
|
<p>Use the same source image to create another VM?</p>
|
|
<div class="btn-row">
|
|
<button onclick="reuseImage(true)" class="btn">Yes — Create Another VM</button>
|
|
<button onclick="reuseImage(false)" class="btn btn-secondary">No — Clean Up & Finish</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<footer>
|
|
<span>vm-bench frontend</span>
|
|
<span>Job: {{ job_id }}</span>
|
|
</footer>
|
|
|
|
<script>
|
|
const jobId = "{{ job_id }}";
|
|
const vmid = "{{ vmid }}";
|
|
const startTime = Date.now();
|
|
let completed = false;
|
|
|
|
async function poll() {
|
|
try {
|
|
const resp = await fetch(`/session/status/${jobId}`);
|
|
const data = await resp.json();
|
|
|
|
const pct = data.progress_percentage || 0;
|
|
document.getElementById('progress-fill').style.width = pct + '%';
|
|
document.getElementById('progress-fill').textContent = pct + '%';
|
|
|
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
document.getElementById('elapsed').textContent = `Elapsed: ${elapsed}s`;
|
|
document.getElementById('status-message').textContent = data.message || '';
|
|
|
|
const badge = document.getElementById('status-badge');
|
|
const map = {
|
|
'queued': ['badge-queued', 'Queued'],
|
|
'processing_conversion': ['badge-running', 'Converting...'],
|
|
'importing_storage': ['badge-running', 'Importing...'],
|
|
'completed': ['badge-completed', 'Completed'],
|
|
'failed': ['badge-failed', 'Failed'],
|
|
};
|
|
const [cls, text] = map[data.status] || ['badge-queued', data.status];
|
|
badge.className = 'badge ' + cls;
|
|
badge.innerHTML = (data.status === 'processing_conversion' || data.status === 'importing_storage')
|
|
? '<span class="spinner"></span> ' + text : text;
|
|
|
|
if (data.error_details) {
|
|
document.getElementById('error-block').classList.remove('hidden');
|
|
document.getElementById('error-block').textContent = data.error_details;
|
|
}
|
|
|
|
if (data.status === 'completed' || data.status === 'failed') {
|
|
completed = true;
|
|
if (data.status === 'completed') {
|
|
document.getElementById('reuse-section').classList.remove('hidden');
|
|
document.getElementById('reuse-section').scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!completed) setTimeout(poll, 2000);
|
|
} catch (err) {
|
|
document.getElementById('error-block').classList.remove('hidden');
|
|
document.getElementById('error-block').textContent = 'Polling error: ' + err.message;
|
|
if (!completed) setTimeout(poll, 5000);
|
|
}
|
|
}
|
|
|
|
async function reuseImage(keep) {
|
|
try {
|
|
await fetch(`/session/cleanup/${jobId}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ delete_staging_files: !keep }),
|
|
});
|
|
if (keep) {
|
|
window.location.href = `/?vmid=${vmid}&source={{ source_filename }}`;
|
|
} else {
|
|
window.location.href = '/';
|
|
}
|
|
} catch (err) {
|
|
alert('Cleanup failed: ' + err.message);
|
|
}
|
|
}
|
|
|
|
// Wait 3 seconds before first poll (backend needs time to queue)
|
|
setTimeout(poll, 3000);
|
|
</script>
|
|
</body>
|
|
</html>
|