fix: replace storage.cfg parser with pvesm status for reliable pool discovery; add Custom... dropdown option

This commit is contained in:
Claus Lohmar 2026-07-27 08:52:34 +00:00
parent e53fcacaa6
commit 2e0c6cdba7
4 changed files with 58 additions and 33 deletions

View file

@ -97,41 +97,33 @@ def health() -> HealthResponse:
@app.get(f"{API_PREFIX}/storage/pools") @app.get(f"{API_PREFIX}/storage/pools")
def storage_pools(): def storage_pools():
"""Return available Proxmox storage pools that support VM disk images.""" """Return available Proxmox storage pools that support VM disk images."""
pools = [] result = []
try: try:
cfg = Path("/etc/pve/storage.cfg") import subprocess
if cfg.exists(): out = subprocess.run(["pvesm", "status"], capture_output=True, text=True, timeout=10)
lines = cfg.read_text() for line in out.stdout.split("\n")[1:]:
current = {} parts = line.split()
for line in lines.split("\n"): if len(parts) < 3:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue continue
if ":" in stripped and not line.startswith(" "): name, stype, active = parts[0], parts[1], parts[2]
if current and current.get("name"): if active != "active":
pools.append(current) continue
parts = stripped.split(":", 1) if stype not in ("lvmthin", "zfspool", "rbd", "dir", "nfs"):
stype = parts[0].strip() continue
sname = parts[1].strip() if len(parts) > 1 else "" # Verify content includes images
current = {"name": sname, "type": stype} content = ""
elif line.startswith(" ") and current: try:
if " " in stripped: cp = subprocess.run(["pvesm", "config", name], capture_output=True, text=True, timeout=5)
key, val = stripped.split(" ", 1) for cl in cp.stdout.split("\n"):
current[key] = val if cl.strip().startswith("content"):
if current and current.get("name"): content = cl.strip().split(None, 1)[1] if len(cl.strip().split(None, 1)) > 1 else ""
pools.append(current) except Exception:
pass
if "images" not in content:
continue
result.append({"name": name, "type": stype})
except Exception: except Exception:
pass pass
result = []
for p in pools:
if p.get("type") in ("lvmthin", "zfspool", "rbd", "dir"):
content = p.get("content", "")
if "images" in content:
result.append({
"name": p["name"],
"type": p["type"],
})
if not result: if not result:
result.append({"name": "local-lvm", "type": "lvmthin"}) result.append({"name": "local-lvm", "type": "lvmthin"})

View file

@ -65,11 +65,14 @@
<div class="pve-form-group"> <div class="pve-form-group">
<label>Storage Pool</label> <label>Storage Pool</label>
{% if storage_pools %} {% if storage_pools %}
<select name="target_storage" class="pve-select"> <select name="target_storage" class="pve-select" id="storage-select">
{% for pool in storage_pools %} {% for pool in storage_pools %}
<option value="{{ pool.name }}">{{ pool.name }} ({{ pool.type }})</option> <option value="{{ pool.name }}">{{ pool.name }} ({{ pool.type }})</option>
{% endfor %} {% endfor %}
<option value="__custom__">Custom...</option>
</select> </select>
<input type="text" class="pve-input pve-hidden" id="storage-custom"
placeholder="Enter pool name" style="margin-top:0.3rem;">
{% else %} {% else %}
<input type="text" name="target_storage" class="pve-input" value="local-lvm"> <input type="text" name="target_storage" class="pve-input" value="local-lvm">
{% endif %} {% endif %}

View file

@ -314,6 +314,21 @@ function initConfirmForm() {
this.value === 'false' ? 'block' : 'none'; this.value === 'false' ? 'block' : 'none';
}); });
} }
const storageSel = document.getElementById('storage-select');
const storageCustom = document.getElementById('storage-custom');
if (storageSel && storageCustom) {
storageSel.addEventListener('change', function() {
if (this.value === '__custom__') {
storageCustom.classList.remove('pve-hidden');
storageCustom.name = 'target_storage';
this.name = '';
} else {
storageCustom.classList.add('pve-hidden');
storageCustom.name = 'target_storage_custom';
this.name = 'target_storage';
}
});
}
} }
async function submitJob(e) { async function submitJob(e) {

View file

@ -202,6 +202,21 @@ function scpInitConfirmForm() {
this.value === 'false' ? 'block' : 'none'; this.value === 'false' ? 'block' : 'none';
}); });
} }
const storageSel = document.getElementById('storage-select');
const storageCustom = document.getElementById('storage-custom');
if (storageSel && storageCustom) {
storageSel.addEventListener('change', function() {
if (this.value === '__custom__') {
storageCustom.classList.remove('pve-hidden');
storageCustom.name = 'target_storage';
this.name = '';
} else {
storageCustom.classList.add('pve-hidden');
storageCustom.name = 'target_storage_custom';
this.name = 'target_storage';
}
});
}
} }
async function scpSubmitJob(e) { async function scpSubmitJob(e) {