vm-bench/frontend/templates/configure.html
Claus Lohmar b000641827 feat: unified configure page — VM settings + multi-disk selection with per-disk shrink
- New /session/configure/{analysis_id} page with VM config + disk table
- Each disk has radio (boot selection) + shrink checkbox + size input
- Per-disk shrink control — check to enable, set custom size
- Disks sorted smallest first, boot disk pre-selected as largest
- Submit via /session/configure/submit, then /session/status/{job_id} polling
- Landpage download/SCP flow redirects to configure after analysis completes
2026-07-27 10:22:39 +00:00

197 lines
No EOL
7.2 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="pve-hero" style="padding-bottom:0.5rem;">
<h1 style="font-size:1.1rem;">VM Configuration</h1>
<p>Source: <span class="pve-code">{{ source_filename }}</span></p>
</div>
<div class="pve-panel">
<div class="pve-panel-header">
<span class="pve-panel-title">VM Settings</span>
</div>
<div class="pve-panel-body">
<div class="pve-row">
<div class="pve-form-group">
<label>VM ID *</label>
<input type="number" id="config-vmid" class="pve-input" required
min="21000" max="21100" placeholder="e.g. 21050" value="{{ suggested_vmid }}">
</div>
<div class="pve-form-group">
<label>VM Name *</label>
<input type="text" id="config-vmname" class="pve-input" required
placeholder="e.g. my-vm" value="{{ vm_name or '' }}">
</div>
</div>
<div class="pve-row">
<div class="pve-form-group">
<label>Storage Pool</label>
{% if storage_pools %}
<select id="config-storage" class="pve-select">
{% for pool in storage_pools %}
<option value="{{ pool.name }}">{{ pool.name }} ({{ pool.type }})</option>
{% endfor %}
<option value="__custom__">Custom...</option>
</select>
<input type="text" id="config-storage-custom" class="pve-input pve-hidden"
placeholder="Enter pool name" style="margin-top:0.3rem;">
{% else %}
<input type="text" id="config-storage" class="pve-input">
{% endif %}
</div>
<div class="pve-form-group">
<label>Boot Detection</label>
<select id="config-bootdetect" class="pve-select">
<option value="auto" selected>Auto-detect (recommended)</option>
<option value="uefi">UEFI (OVMF)</option>
<option value="legacy">Legacy BIOS (SeaBIOS)</option>
</select>
</div>
</div>
<div class="pve-row">
<div class="pve-form-group">
<label>CPU Cores</label>
<input type="number" id="config-cores" class="pve-input" value="2" min="1">
</div>
<div class="pve-form-group">
<label>RAM (MB)</label>
<input type="number" id="config-ram" class="pve-input" value="4096" min="512">
</div>
</div>
</div>
</div>
<div class="pve-panel">
<div class="pve-panel-header">
<span class="pve-panel-title">Disks Found</span>
</div>
<div class="pve-panel-body">
<table class="pve-table">
<thead>
<tr style="font-size:0.7rem;color:var(--pve-muted);">
<td style="width:40px;">Boot</td>
<td>Filename</td>
<td style="width:60px;">Format</td>
<td style="width:80px;">Size</td>
<td style="width:140px;">Resize</td>
</tr>
</thead>
<tbody id="disks-tbody">
{% for d in disks %}
<tr>
<td><input type="radio" name="boot_disk" value="{{ loop.index0 }}" {% if loop.first %}checked{% endif %}></td>
<td>{{ d.filename }}</td>
<td>{{ d.format }}</td>
<td>{{ d.size_gb }} GB</td>
<td>
<label style="display:flex;align-items:center;gap:0.3rem;font-size:0.72rem;white-space:nowrap;">
<input type="checkbox" class="disk-shrink-cb" onchange="toggleShrink(this, {{ loop.index0 }})">
to <input type="number" class="pve-input disk-shrink-size" id="shrink-{{ loop.index0 }}"
value="{{ d.size_gb }}" disabled style="width:70px;padding:0.2rem 0.3rem;font-size:0.72rem;"> GB
</label>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pve-alert pve-alert-info" style="margin-top:0.75rem;">
<strong>TIP:</strong> The disk with the operating system should be the boot disk.
Data disks will be attached as additional drives.
</div>
<button id="config-submit-btn" class="pve-btn pve-btn-primary" style="margin-top:0.75rem;" onclick="submitConfig()">Start Conversion</button>
<div id="config-submit-status" class="pve-hidden" style="margin-top:0.5rem;"></div>
</div>
</div>
<script>
var _analysisId = '{{ analysis_id }}';
var _sessionId = '{{ session_id }}';
function toggleShrink(cb, idx) {
var inp = document.getElementById('shrink-' + idx);
if (cb.checked) { inp.removeAttribute('disabled'); inp.focus(); }
else { inp.setAttribute('disabled', ''); }
}
(function(){
var ss = document.getElementById('config-storage');
var sc = document.getElementById('config-storage-custom');
if (ss && sc) {
ss.addEventListener('change', function() {
if (this.value === '__custom__') {
sc.classList.remove('pve-hidden'); sc.focus();
} else {
sc.classList.add('pve-hidden');
}
});
}
})();
async function submitConfig() {
var vmid = parseInt(document.getElementById('config-vmid').value) || 0;
var vmName = document.getElementById('config-vmname').value.trim();
if (!vmid || vmid < 21000 || vmid > 21100) { alert('VM ID must be 21000-21100.'); return; }
if (!vmName) { alert('Please enter a VM name.'); return; }
var storage = document.getElementById('config-storage');
var sc = document.getElementById('config-storage-custom');
var targetStorage = sc && !sc.classList.contains('pve-hidden') ? sc.value : (storage ? storage.value : '');
var bootDetect = document.getElementById('config-bootdetect').value;
var autoBoot = bootDetect === 'auto';
var bootType = autoBoot ? 'uefi' : bootDetect;
var cores = parseInt(document.getElementById('config-cores').value) || 2;
var ram = parseInt(document.getElementById('config-ram').value) || 4096;
var bootIdx = 0;
var radios = document.getElementsByName('boot_disk');
for (var i = 0; i < radios.length; i++) {
if (radios[i].checked) { bootIdx = parseInt(radios[i].value); break; }
}
var payload = {
vmid: vmid, vm_name: vmName, cpu_cores: cores, ram_mb: ram,
target_storage: targetStorage, auto_detect_boot: autoBoot, boot_type: bootType,
session_id: _sessionId, analysis_id: _analysisId,
boot_disk: null, additional_disks: []
};
var shrinkCbs = document.getElementsByClassName('disk-shrink-cb');
var diskFiles = [{% for d in disks %}{{ d|tojson }},{% endfor %}];
for (var i = 0; i < shrinkCbs.length; i++) {
var d = diskFiles[i];
var spec = { disk_type: 'image_file', source_filename: d.filename, format: d.format };
if (shrinkCbs[i].checked) {
var si = document.getElementById('shrink-' + i);
var sz = si ? parseInt(si.value) || null : null;
if (sz) spec.target_disk_size_gb = sz;
}
if (i === bootIdx) payload.boot_disk = spec;
else payload.additional_disks.push(spec);
}
var btn = document.getElementById('config-submit-btn');
var status = document.getElementById('config-submit-status');
btn.disabled = true;
status.classList.remove('pve-hidden');
status.innerHTML = '<div class="pve-spinner"></div> Submitting job...';
try {
var resp = await fetch('/session/configure/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
var data = await resp.json();
if (data.error) { throw new Error(data.error); }
window.location.href = '/session/status/' + data.job_id;
} catch (err) {
status.innerHTML = '<div class="pve-alert pve-alert-error">Failed: ' + err.message + '</div>';
btn.disabled = false;
}
}
</script>
{% endblock %}