feat: landing page with Download, SCP Pull, and Resume cards
- Hero header at top describing the app - Two side-by-side cards: Download (URL) and SCP Pull - Resume sessions section appears above cards when sessions exist - Download card expands to inline form - Resume asks for VM Name/ID only, skips URL field - Clean separation of entry points
This commit is contained in:
parent
162110b4b1
commit
df6b8085ca
2 changed files with 125 additions and 41 deletions
|
|
@ -293,3 +293,50 @@ body {
|
||||||
.pve-divider { border: none; border-top: 1px solid var(--pve-border); margin: 1rem 0; }
|
.pve-divider { border: none; border-top: 1px solid var(--pve-border); margin: 1rem 0; }
|
||||||
.pve-code { background: #f5f5f5; padding: 0.1rem 0.4rem; border-radius: 2px; font-size: 0.78rem; }
|
.pve-code { background: #f5f5f5; padding: 0.1rem 0.4rem; border-radius: 2px; font-size: 0.78rem; }
|
||||||
.pve-center { text-align: center; }
|
.pve-center { text-align: center; }
|
||||||
|
|
||||||
|
/* ── Landing page ─────────────────────────────────────────────── */
|
||||||
|
.pve-hero {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.8rem 1rem 1.2rem;
|
||||||
|
}
|
||||||
|
.pve-hero h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--pve-text);
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
.pve-hero p {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--pve-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 520px) { .landing-cards { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
.landing-card {
|
||||||
|
background: var(--pve-panel-bg);
|
||||||
|
border: 1px solid var(--pve-border);
|
||||||
|
border-radius: var(--pve-radius);
|
||||||
|
padding: 1.2rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
cursor: default;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
|
||||||
|
}
|
||||||
|
.landing-card:hover { border-color: var(--pve-accent); }
|
||||||
|
.landing-card-icon { font-size: 1.6rem; margin-bottom: 0.4rem; }
|
||||||
|
.landing-card h3 {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
.landing-card p {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--pve-muted);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,62 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="pve-hero">
|
||||||
|
<h1>VM Bench</h1>
|
||||||
|
<p>Convert VMware images to Proxmox VMs — automatic OS detection, disk shrink, EFI probing.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="resume-sessions" class="pve-panel pve-hidden">
|
<div id="resume-sessions" class="pve-panel pve-hidden">
|
||||||
<div class="pve-panel-header">
|
<div class="pve-panel-header">
|
||||||
<span class="pve-panel-title">Resume Previous Session</span>
|
<span class="pve-panel-title">Resume Previous Sessions</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pve-panel-body" id="resume-sessions-list">
|
<div class="pve-panel-body" id="resume-sessions-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="landing-cards">
|
||||||
|
<div class="landing-card" id="card-download">
|
||||||
|
<div class="landing-card-icon">📥</div>
|
||||||
|
<h3>Download</h3>
|
||||||
|
<p>Pull a disk image or archive from a URL via high-speed download.</p>
|
||||||
|
<button class="pve-btn pve-btn-primary" onclick="showDownload()">Start</button>
|
||||||
|
</div>
|
||||||
|
<div class="landing-card" id="card-scp">
|
||||||
|
<div class="landing-card-icon">📡</div>
|
||||||
|
<h3>SCP Pull</h3>
|
||||||
|
<p>Transfer files directly from a remote server via SCP — no middle hop.</p>
|
||||||
|
<a href="/scp" class="pve-btn pve-btn-primary">Start</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pve-panel" id="step1">
|
<div id="download-section" class="pve-panel pve-hidden">
|
||||||
<div class="pve-panel-header">
|
<div class="pve-panel-header">
|
||||||
<span class="pve-panel-title">New Conversion Session</span>
|
<span class="pve-panel-title" id="download-title">New Download Session</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pve-panel-body">
|
<div class="pve-panel-body">
|
||||||
|
|
||||||
<form id="session-form" onsubmit="startSession(event)">
|
<form id="session-form" onsubmit="startSession(event)">
|
||||||
<div class="pve-form-group">
|
<div class="pve-row">
|
||||||
<label for="vm-name">VM Name *</label>
|
<div class="pve-form-group">
|
||||||
<input type="text" id="vm-name" name="vm_name" required class="pve-input"
|
<label>VM Name *</label>
|
||||||
placeholder="e.g. debian-test" value="{{ prefill_vmname or '' }}">
|
<input type="text" id="vm-name" name="vm_name" required class="pve-input"
|
||||||
<span class="pve-hint">Display name for the VM on the Proxmox host.</span>
|
placeholder="e.g. debian-test" value="{{ prefill_vmname or '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="pve-form-group">
|
||||||
|
<label>VM ID *</label>
|
||||||
|
<input type="number" id="vmid" name="vmid" required class="pve-input"
|
||||||
|
placeholder="e.g. 21050" min="21000" max="21100">
|
||||||
|
<span class="pve-hint">Range 21000–21100. Must be unique on host.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pve-form-group" id="url-group">
|
||||||
<div class="pve-form-group">
|
<label>Download URL *</label>
|
||||||
<label for="vmid">VM ID *</label>
|
|
||||||
<input type="number" id="vmid" name="vmid" required class="pve-input"
|
|
||||||
placeholder="e.g. 21050" min="21000" max="21100">
|
|
||||||
<span class="pve-hint">Range 21000–21100. Must be unique on the Proxmox host.</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pve-form-group">
|
|
||||||
<label for="source-url">Download URL *</label>
|
|
||||||
<input type="url" id="source-url" name="source_url" required class="pve-input"
|
<input type="url" id="source-url" name="source_url" required class="pve-input"
|
||||||
placeholder="https://example.com/image.7z">
|
placeholder="https://example.com/image.7z">
|
||||||
<span class="pve-hint">Direct URL to a disk image or archive (.vmdk, .7z, .zip, etc.)</span>
|
<span class="pve-hint">Direct URL to a disk image or archive (.vmdk, .7z, .zip, etc.)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" id="start-btn" class="pve-btn pve-btn-primary">Download & Analyse</button>
|
<button type="submit" id="start-btn" class="pve-btn pve-btn-primary">Download & Analyse</button>
|
||||||
|
<button type="button" class="pve-btn" onclick="hideDownload()" style="margin-left:0.5rem;">Cancel</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr class="pve-divider">
|
|
||||||
<p class="pve-dim" style="font-size:0.78rem; margin-bottom:0.5rem;">
|
|
||||||
Files on a remote server? Pull directly via SCP:
|
|
||||||
</p>
|
|
||||||
<a href="/scp" class="pve-btn">SCP Pull</a>
|
|
||||||
|
|
||||||
<!-- Progress area -->
|
|
||||||
<div id="session-status" class="pve-hidden" style="margin-top:1rem;">
|
<div id="session-status" class="pve-hidden" style="margin-top:1rem;">
|
||||||
<div id="phase-label" style="margin-bottom:0.5rem;"></div>
|
<div id="phase-label" style="margin-bottom:0.5rem;"></div>
|
||||||
<div class="pve-progress">
|
<div class="pve-progress">
|
||||||
|
|
@ -63,6 +73,24 @@
|
||||||
<script>
|
<script>
|
||||||
loadSessions();
|
loadSessions();
|
||||||
|
|
||||||
|
function showDownload() {
|
||||||
|
document.getElementById('card-download').style.display = 'none';
|
||||||
|
document.getElementById('card-scp').style.display = 'none';
|
||||||
|
document.getElementById('download-section').classList.remove('pve-hidden');
|
||||||
|
document.getElementById('download-title').textContent = 'New Download Session';
|
||||||
|
document.getElementById('url-group').classList.remove('pve-hidden');
|
||||||
|
document.getElementById('start-btn').textContent = 'Download & Analyse';
|
||||||
|
document.getElementById('start-btn').onclick = null; // restore form submit
|
||||||
|
document.getElementById('source-url').required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDownload() {
|
||||||
|
document.getElementById('card-download').style.display = '';
|
||||||
|
document.getElementById('card-scp').style.display = '';
|
||||||
|
document.getElementById('download-section').classList.add('pve-hidden');
|
||||||
|
window.RESUMED_SID = null;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSessions() {
|
async function loadSessions() {
|
||||||
try {
|
try {
|
||||||
var resp = await fetch('/session/sessions');
|
var resp = await fetch('/session/sessions');
|
||||||
|
|
@ -89,17 +117,26 @@ async function loadSessions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resumeSession(sessionId, firstFile) {
|
function resumeSession(sessionId, firstFile) {
|
||||||
var vmid = document.getElementById('vmid').value;
|
document.getElementById('card-download').style.display = 'none';
|
||||||
var vmName = document.getElementById('vm-name').value.trim();
|
document.getElementById('card-scp').style.display = 'none';
|
||||||
if (!vmid) { alert('Please enter a VM ID first.'); return; }
|
document.getElementById('download-section').classList.remove('pve-hidden');
|
||||||
window.RESUMED_SID = sessionId;
|
document.getElementById('download-title').textContent = 'Resume Session';
|
||||||
var ses = document.getElementById('resume-sessions');
|
document.getElementById('url-group').classList.add('pve-hidden');
|
||||||
ses.classList.add('pve-hidden');
|
var btn = document.getElementById('start-btn');
|
||||||
document.getElementById('step1').style.display = 'none';
|
btn.textContent = 'Resume Analysis';
|
||||||
var status = document.getElementById('session-status');
|
btn.onclick = function(e) {
|
||||||
status.classList.remove('pve-hidden');
|
e.preventDefault();
|
||||||
setPhase('Analysing source image...', 0, true);
|
var vmid = document.getElementById('vmid').value;
|
||||||
runAnalysis(vmid, firstFile, vmName, sessionId);
|
var vmName = document.getElementById('vm-name').value.trim();
|
||||||
|
if (!vmid) { alert('Please enter a VM ID.'); return; }
|
||||||
|
window.RESUMED_SID = sessionId;
|
||||||
|
document.getElementById('resume-sessions').classList.add('pve-hidden');
|
||||||
|
var status = document.getElementById('session-status');
|
||||||
|
status.classList.remove('pve-hidden');
|
||||||
|
setPhase('Analysing source image...', 0, true);
|
||||||
|
runAnalysis(vmid, firstFile, vmName, sessionId);
|
||||||
|
};
|
||||||
|
document.getElementById('source-url').required = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPhase(label, pct, isSpinner) {
|
function setPhase(label, pct, isSpinner) {
|
||||||
|
|
@ -278,7 +315,7 @@ async function submitJob(e) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/session/confirm', { method: 'POST', body: formData });
|
const resp = await fetch('/session/confirm', { method: 'POST', body: formData });
|
||||||
const html = await resp.text();
|
const html = await resp.text();
|
||||||
document.getElementById('step1').style.display = 'none';
|
document.getElementById('download-section').style.display = 'none';
|
||||||
document.getElementById('analysis-section').innerHTML = '';
|
document.getElementById('analysis-section').innerHTML = '';
|
||||||
document.getElementById('polling-section').innerHTML = html;
|
document.getElementById('polling-section').innerHTML = html;
|
||||||
initPolling();
|
initPolling();
|
||||||
|
|
@ -376,4 +413,4 @@ async function copyVM(jobId, vmid, sourceFilename) {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Reference in a new issue