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

View file

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

View file

@ -314,6 +314,21 @@ function initConfirmForm() {
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) {

View file

@ -202,6 +202,21 @@ function scpInitConfirmForm() {
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) {