diff --git a/backend/app.py b/backend/app.py index c24771d..e54a877 100644 --- a/backend/app.py +++ b/backend/app.py @@ -94,6 +94,49 @@ def health() -> HealthResponse: return HealthResponse(status="ok") +@app.get(f"{API_PREFIX}/storage/pools") +def storage_pools(): + """Return available Proxmox storage pools that support disk images.""" + pools = [] + try: + cfg = Path("/etc/pve/storage.cfg") + if cfg.exists(): + lines = cfg.read_text() + current = {} + for line in lines.split("\n"): + line = line.strip() + if not line or line.startswith("#"): + continue + if ":" in line and not line.startswith(" "): + if current and current.get("name"): + pools.append(current) + parts = line.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 line: + key, val = line.strip().split(" ", 1) + current[key] = val + if current and current.get("name"): + pools.append(current) + except Exception: + pass + + result = [] + for p in pools: + if p.get("type") in ("lvmthin", "zfspool", "rbd", "dir"): + result.append({ + "name": p["name"], + "type": p["type"], + }) + + if not result: + result.append({"name": "local-lvm", "type": "lvmthin"}) + + return {"pools": result} + + @app.post(f"{API_PREFIX}/analyze", response_model=AnalyzeStatusResponse, status_code=202) def analyze(req: AnalyzeRequest) -> AnalyzeStatusResponse: """Analyze a source file asynchronously: extraction + disk probing + OS detection.""" diff --git a/frontend/api_client.py b/frontend/api_client.py index 612379f..43c8aef 100644 --- a/frontend/api_client.py +++ b/frontend/api_client.py @@ -54,6 +54,10 @@ class ApiClient: """GET /api/v1/analyze/{analysis_id} — poll analysis status.""" return self._get(f"/api/v1/analyze/{analysis_id}") + def get_storage_pools(self) -> dict: + """GET /api/v1/storage/pools — list available Proxmox storage pools.""" + return self._get("/api/v1/storage/pools") + def create_job(self, payload: dict) -> dict: """POST /api/v1/jobs — submit conversion job.""" return self._post("/api/v1/jobs", payload) diff --git a/frontend/app.py b/frontend/app.py index 9c2c9ab..87b1b46 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -356,6 +356,13 @@ async def session_analyze_result( return render("_analysis.html", request=request, error=f"Failed to fetch analysis: {exc.detail}") + pools = [] + try: + sp = api.get_storage_pools() + pools = sp.get("pools", []) + except Exception: + pass + analysis = result name = vm_name.strip() if not name: @@ -363,7 +370,7 @@ async def session_analyze_result( return render("_analysis.html", request=request, vmid=vmid, source_filename=source_filename, - vm_name=name, analysis=analysis) + vm_name=name, analysis=analysis, storage_pools=pools) @app.post("/session/confirm", response_class=HTMLResponse) diff --git a/frontend/templates/_analysis.html b/frontend/templates/_analysis.html index 5dd8484..b9e7ebf 100644 --- a/frontend/templates/_analysis.html +++ b/frontend/templates/_analysis.html @@ -63,7 +63,16 @@