- 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
197 lines
No EOL
7.2 KiB
HTML
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 %} |