- _parse_vmx_config reads numvcpus, memSize, firmware, guestOS from .vmx - Configure page pre-fills CPU cores, RAM, boot type, VM name from VMX - Banner shows detected config: 'VMware VMX config detected. Settings pre-filled' - Works alongside OVF detection — OVF takes priority if both present
222 lines
No EOL
8.7 KiB
HTML
222 lines
No EOL
8.7 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>
|
|
{% if ovf_path %}
|
|
<div class="pve-alert pve-alert-info" style="margin-top:0.5rem;">
|
|
<strong>OVA/OVF appliance detected.</strong> qm importovf will read the VM configuration
|
|
from the appliance descriptor. Disks are imported together — individual shrink is not available.
|
|
</div>
|
|
{% elif vmx_config and vmx_config|length > 0 %}
|
|
<div class="pve-alert pve-alert-info" style="margin-top:0.5rem;">
|
|
<strong>VMware VMX config detected.</strong> Settings pre-filled from {{ vmx_config.os_type or 'VM' }} configuration.
|
|
CPU: {{ vmx_config.cpu_cores or '?' }} cores, RAM: {{ vmx_config.ram_mb or '?' }} MB, Boot: {{ vmx_config.boot_type or 'auto' }}.
|
|
</div>
|
|
{% endif %}
|
|
</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="{{ vmx_config.os_type or 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" {% if not vmx_config.boot_type %}selected{% endif %}>Auto-detect (recommended)</option>
|
|
<option value="uefi" {% if vmx_config.boot_type == 'uefi' %}selected{% endif %}>UEFI (OVMF)</option>
|
|
<option value="legacy" {% if vmx_config.boot_type == 'legacy' %}selected{% endif %}>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="{{ vmx_config.cpu_cores or 2 }}" min="1">
|
|
</div>
|
|
<div class="pve-form-group">
|
|
<label>RAM (MB)</label>
|
|
<input type="number" id="config-ram" class="pve-input" value="{{ vmx_config.ram_mb or 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;">
|
|
{% if ovf_path %}
|
|
<span class="pve-dim">handled by OVF</span>
|
|
{% else %}
|
|
<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
|
|
{% endif %}
|
|
</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 }}';
|
|
var _ovfPath = '{{ ovf_path or "" }}';
|
|
|
|
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: []
|
|
};
|
|
if (_ovfPath) payload.ovf_path = _ovfPath;
|
|
|
|
var shrinkCbs = document.getElementsByClassName('disk-shrink-cb');
|
|
var diskFiles = [{% for d in disks %}{{ d|tojson }},{% endfor %}];
|
|
|
|
if (shrinkCbs.length === 0 && diskFiles.length > 0) {
|
|
payload.boot_disk = { disk_type: 'image_file', source_filename: diskFiles[0].filename, format: diskFiles[0].format };
|
|
for (var k = 1; k < diskFiles.length; k++) {
|
|
payload.additional_disks.push({ disk_type: 'image_file', source_filename: diskFiles[k].filename, format: diskFiles[k].format });
|
|
}
|
|
} else {
|
|
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 %} |