diff --git a/backend/app.py b/backend/app.py index 4f11834..b06e7b8 100644 --- a/backend/app.py +++ b/backend/app.py @@ -97,42 +97,34 @@ 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("#"): - 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) + 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 + 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"}) diff --git a/frontend/templates/_analysis.html b/frontend/templates/_analysis.html index f4cc31e..83896aa 100644 --- a/frontend/templates/_analysis.html +++ b/frontend/templates/_analysis.html @@ -65,11 +65,14 @@
{% if storage_pools %} - {% for pool in storage_pools %} {% endfor %} + + {% else %} {% endif %} diff --git a/frontend/templates/index.html b/frontend/templates/index.html index 501718e..97c331e 100644 --- a/frontend/templates/index.html +++ b/frontend/templates/index.html @@ -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) { diff --git a/frontend/templates/scp.html b/frontend/templates/scp.html index 69e7445..7065ecc 100644 --- a/frontend/templates/scp.html +++ b/frontend/templates/scp.html @@ -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) {