from fastapi import APIRouter, Request, HTTPException from fastapi.responses import HTMLResponse import api_client import config import json from pathlib import Path router = APIRouter() @router.get("/backend-config", response_class=HTMLResponse) async def backend_config(request: Request): """Affiche le formulaire de configuration du backend""" current_backend_url = config.BACKEND_URL current_potree_url = config.POTREE_URL return request.app.state.templates.TemplateResponse( "partials/backend_config.html", {"request": request, "current_backend_url": current_backend_url, "current_potree_url": current_potree_url}, ) @router.post("/backend-config", response_class=HTMLResponse) async def save_backend_config(request: Request): """Sauvegarde les nouvelles URLs du backend et Potree""" form_data = await request.form() new_backend_url = form_data.get("backend_url", "").strip().rstrip("/") new_potree_url = form_data.get("potree_url", "").strip().rstrip("/") if not new_backend_url: return request.app.state.templates.TemplateResponse( "partials/backend_config.html", {"request": request, "current_backend_url": config.BACKEND_URL, "current_potree_url": config.POTREE_URL, "error": "URL du backend vide"}, ) config_file = Path(__file__).parent.parent / "config" / "backend.json" config_file.parent.mkdir(parents=True, exist_ok=True) config_data = {} if new_backend_url: config_data["backend_url"] = new_backend_url if new_potree_url: config_data["potree_url"] = new_potree_url with open(config_file, "w") as f: json.dump(config_data, f, indent=2) config.BACKEND_URL = new_backend_url config.POTREE_URL = new_potree_url return request.app.state.templates.TemplateResponse( "partials/backend_config.html", {"request": request, "current_backend_url": new_backend_url, "current_potree_url": new_potree_url, "success": True}, ) @router.get("/admin/list", response_class=HTMLResponse) async def admin_list(request: Request): """ Liste via l'api_client (tab Admin). Retourne cloud_list_body.html pour injection dans #cloud-list-body. """ try: pointclouds = await api_client.list_pointclouds() except Exception as e: return request.app.state.templates.TemplateResponse( "partials/cloud_list_body.html", {"request": request, "pointclouds": [], "error": str(e)}, ) return request.app.state.templates.TemplateResponse( "partials/cloud_list_body.html", {"request": request, "pointclouds": pointclouds}, ) @router.get("/admin/debug/{pc_id}", response_class=HTMLResponse) async def admin_debug(request: Request, pc_id: str): """Affiche les informations de debug pour un nuage""" debug_info = await api_client.get_debug(pc_id) return request.app.state.templates.TemplateResponse( "partials/debug_panel.html", {"request": request, "pc_id": pc_id, "data": debug_info}, ) @router.delete("/admin/delete/{pc_id}", response_class=HTMLResponse) async def admin_delete(request: Request, pc_id: str): """Supprime un nuage de points et rafraîchit la liste""" try: await api_client.delete_pointcloud(pc_id) except Exception as e: return request.app.state.templates.TemplateResponse( "partials/cloud_list_body.html", {"request": request, "pointclouds": [], "error": f"Erreur suppression : {str(e)}"}, ) # Après suppression, on retourne la liste mise à jour from pathlib import Path as _Path pointclouds = [] ept_dir = _Path(config.EPT_DIR) if ept_dir.exists(): for item in sorted(ept_dir.iterdir(), key=lambda x: x.stat().st_ctime, reverse=True): if item.is_dir(): total_size = sum(f.stat().st_size for f in item.rglob("*") if f.is_file()) file_count = sum(1 for f in item.rglob("*") if f.is_file()) if file_count > 0: pointclouds.append({ "id": item.name, "size_mb": round(total_size / (1024 * 1024), 2), "file_count": file_count, "manifest": {}, "created": item.stat().st_ctime, }) return request.app.state.templates.TemplateResponse( "partials/cloud_list_body.html", {"request": request, "pointclouds": pointclouds}, ) @router.post("/admin/crop/{pc_id}", response_class=HTMLResponse) async def admin_crop(request: Request, pc_id: str): """Crop via api_client — lit le body form (appelé depuis crop_section.html)""" form_data = await request.form() try: box = { "minX": float(form_data.get("minX", 0)), "minY": float(form_data.get("minY", 0)), "minZ": float(form_data.get("minZ", 0)), "maxX": float(form_data.get("maxX", 0)), "maxY": float(form_data.get("maxY", 0)), "maxZ": float(form_data.get("maxZ", 0)), } except (TypeError, ValueError) as e: return request.app.state.templates.TemplateResponse( "partials/upload_result.html", {"request": request, "error": f"Coordonnées invalides : {str(e)}", "result": None}, ) try: result = await api_client.crop_pointcloud(pc_id, box) if result.get("ok"): return request.app.state.templates.TemplateResponse( "partials/upload_result.html", {"request": request, "result": { "id": result.get("id"), "filename": f"{pc_id}_cropped.las", "size_mb": result.get("size_mb", 0), "conversion_time_seconds": result.get("conversion_time_seconds", 0), }, "error": None}, ) else: return request.app.state.templates.TemplateResponse( "partials/upload_result.html", {"request": request, "error": result.get("detail", "Erreur inconnue"), "result": None}, ) except Exception as e: return request.app.state.templates.TemplateResponse( "partials/upload_result.html", {"request": request, "error": str(e), "result": None}, )