vm-bench/frontend/templates/index.html
Claus Lohmar 509eb97b57 fix: large-file support + user-defined VM name
Large-file handling:
- Set TMPDIR=/mnt/converter/in in service to spool uploads to
  shared storage instead of 24 GB LXC rootfs (critical for >24GB)
- Chunked upload streaming (8 MiB) with progress logging every 1 GiB
- Pre-flight disk space check via Content-Length header
- Clean up partial files on upload/download failure
- Download timeout extended to 7200s (2 hours) for 88 GB images
- Switched wget from --show-progress to --progress=dot:giga
  (compact output, won't fill memory on large transfers)
- uvicorn --timeout-keep-alive 300 on both frontend and backend

VM name:
- Added vm_name field to initial session form (step 1)
- Falls back to auto-generated 'os_type-vmid' if left blank
- Pre-filled & editable in confirm form (step 2)
2026-07-21 17:59:31 +00:00

106 lines
3.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block content %}
<!-- Step 1: Start New Session -->
<div class="panel" id="step1">
<h2>New Conversion Session</h2>
<form id="session-form" onsubmit="startSession(event)">
<div class="form-group">
<label for="vm-name">VM Name *</label>
<input type="text" id="vm-name" name="vm_name" required
placeholder="e.g. debian-test" value="{{ prefill_vmname or '' }}">
<span class="hint">Display name for the VM on the Proxmox host.</span>
</div>
<div class="form-group">
<label for="vmid">VM ID *</label>
<input type="number" id="vmid" name="vmid" required
placeholder="e.g. 21050" min="21000" max="21100">
<span class="hint">Range 2100021100. Must be unique on the Proxmox host.</span>
</div>
<div class="form-group">
<label for="source-type">Source Type</label>
<select id="source-type" onchange="toggleSourceInput(event)">
<option value="upload">File Upload</option>
<option value="url">Download from URL</option>
</select>
</div>
<div class="form-group" id="upload-group">
<label for="source-file">Source File (.vmdk / .7z / .zip / .tar.gz)</label>
<input type="file" id="source-file" name="source_file"
accept=".vmdk,.vhd,.vhdx,.7z,.zip,.tar.gz,.tgz,.tar,.gz">
</div>
<div class="form-group hidden" id="url-group">
<label for="source-url">Download URL</label>
<input type="url" id="source-url" name="source_url"
placeholder="https://example.com/image.7z">
</div>
<button type="submit" id="start-btn">Analyse Source Image</button>
</form>
<div id="session-status" class="hidden" style="margin-top:1rem;"></div>
</div>
<!-- Step 2: Analysis Result (populated by JavaScript) -->
<div id="analysis-section"></div>
<script>
function toggleSourceInput(e) {
const type = e.target.value;
document.getElementById('upload-group').classList.toggle('hidden', type !== 'upload');
document.getElementById('url-group').classList.toggle('hidden', type !== 'url');
}
async function startSession(e) {
e.preventDefault();
const btn = document.getElementById('start-btn');
const status = document.getElementById('session-status');
btn.disabled = true;
status.classList.remove('hidden');
status.innerHTML = '<div class="spinner"></div> Analysing source image...';
const formData = new FormData();
formData.append('vmid', document.getElementById('vmid').value);
formData.append('vm_name', document.getElementById('vm-name').value);
const sourceType = document.getElementById('source-type').value;
formData.append('source_type', sourceType);
if (sourceType === 'upload') {
const fileInput = document.getElementById('source-file');
if (fileInput.files.length > 0) {
formData.append('source_file', fileInput.files[0]);
} else {
status.innerHTML = '<div class="error">Please select a file.</div>';
btn.disabled = false;
return;
}
} else {
const url = document.getElementById('source-url').value.trim();
if (!url) {
status.innerHTML = '<div class="error">Please enter a URL.</div>';
btn.disabled = false;
return;
}
formData.append('source_url', url);
}
try {
const resp = await fetch('/session/start', { method: 'POST', body: formData });
const html = await resp.text();
document.getElementById('analysis-section').innerHTML = html;
status.classList.add('hidden');
} catch (err) {
status.innerHTML = `<div class="error">Failed: ${err.message}</div>`;
} finally {
btn.disabled = false;
}
}
</script>
{% endblock %}