Upload files to "frontend/routes"

This commit is contained in:
Thierry 2026-04-01 23:27:04 +02:00
parent c4b3c41718
commit ff2413df86
2 changed files with 78 additions and 58 deletions

View file

@ -32,7 +32,6 @@ async def save_backend_config(request: Request):
{"request": request, "current_backend_url": config.BACKEND_URL, "current_potree_url": config.POTREE_URL, "error": "URL du backend vide"}, {"request": request, "current_backend_url": config.BACKEND_URL, "current_potree_url": config.POTREE_URL, "error": "URL du backend vide"},
) )
# Sauvegarder dans le fichier JSON
config_file = Path(__file__).parent.parent / "config" / "backend.json" config_file = Path(__file__).parent.parent / "config" / "backend.json"
config_file.parent.mkdir(parents=True, exist_ok=True) config_file.parent.mkdir(parents=True, exist_ok=True)
@ -45,7 +44,6 @@ async def save_backend_config(request: Request):
with open(config_file, "w") as f: with open(config_file, "w") as f:
json.dump(config_data, f, indent=2) json.dump(config_data, f, indent=2)
# Mettre à jour les variables module
config.BACKEND_URL = new_backend_url config.BACKEND_URL = new_backend_url
config.POTREE_URL = new_potree_url config.POTREE_URL = new_potree_url
@ -55,17 +53,26 @@ async def save_backend_config(request: Request):
) )
@router.get("/list", response_class=HTMLResponse) @router.get("/admin/list", response_class=HTMLResponse)
async def admin_list(request: Request): async def admin_list(request: Request):
"""Affiche la liste de tous les nuages de points""" """
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() pointclouds = await api_client.list_pointclouds()
except Exception as e:
return request.app.state.templates.TemplateResponse( return request.app.state.templates.TemplateResponse(
"partials/cloud_list.html", "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}, {"request": request, "pointclouds": pointclouds},
) )
@router.get("/debug/{pc_id}", response_class=HTMLResponse) @router.get("/admin/debug/{pc_id}", response_class=HTMLResponse)
async def admin_debug(request: Request, pc_id: str): async def admin_debug(request: Request, pc_id: str):
"""Affiche les informations de debug pour un nuage""" """Affiche les informations de debug pour un nuage"""
debug_info = await api_client.get_debug(pc_id) debug_info = await api_client.get_debug(pc_id)
@ -75,40 +82,62 @@ async def admin_debug(request: Request, pc_id: str):
) )
@router.delete("/delete/{pc_id}", response_class=HTMLResponse) @router.delete("/admin/delete/{pc_id}", response_class=HTMLResponse)
async def admin_delete(request: Request, pc_id: str): async def admin_delete(request: Request, pc_id: str):
"""Supprime un nuage de points""" """Supprime un nuage de points et rafraîchit la liste"""
try: try:
result = await api_client.delete_pointcloud(pc_id) await api_client.delete_pointcloud(pc_id)
return request.app.state.templates.TemplateResponse(
"partials/upload_result.html",
{"request": request, "result": {
"id": pc_id,
"filename": f"{pc_id}.las",
"size_mb": 0,
"conversion_time_seconds": 0,
}, "error": None},
)
except Exception as e: except Exception as e:
return request.app.state.templates.TemplateResponse( return request.app.state.templates.TemplateResponse(
"partials/upload_result.html", "partials/cloud_list_body.html",
{"request": request, "error": str(e), "result": None}, {"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("/crop/{pc_id}", response_class=HTMLResponse) @router.post("/admin/crop/{pc_id}", response_class=HTMLResponse)
async def admin_crop(request: Request, pc_id: str, box: dict): async def admin_crop(request: Request, pc_id: str):
""" """Crop via api_client — lit le body form (appelé depuis crop_section.html)"""
Crop le nuage de points avec PDAL. 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},
)
Args:
pc_id: ID du nuage de points à cropper
box: dict avec les coordonnées de la box 3D
{"minX", "minY", "minZ", "maxX", "maxY", "maxZ"}
"""
try: try:
result = await api_client.crop_pointcloud(pc_id, box) result = await api_client.crop_pointcloud(pc_id, box)
if result.get("ok"): if result.get("ok"):
return request.app.state.templates.TemplateResponse( return request.app.state.templates.TemplateResponse(
"partials/upload_result.html", "partials/upload_result.html",

View file

@ -3,8 +3,6 @@ from fastapi.responses import HTMLResponse
import config import config
import api_client import api_client
from pathlib import Path from pathlib import Path
import shutil
import os
from datetime import datetime from datetime import datetime
router = APIRouter() router = APIRouter()
@ -12,24 +10,17 @@ router = APIRouter()
@router.get("/viewer/list", response_class=HTMLResponse) @router.get("/viewer/list", response_class=HTMLResponse)
async def viewer_list(request: Request): async def viewer_list(request: Request):
"""Liste les nuages de points disponibles - Endpoint autonome frontend""" """
Liste les nuages de points retourne uniquement le contenu intérieur
(cloud_list_body.html) pour injection dans #cloud-list-body.
La card enveloppe et le bouton Actualiser vivent dans index.html, pas ici.
"""
pointclouds = [] pointclouds = []
# Récupérer la liste des nuages directement depuis le système de fichiers
ept_dir = Path(config.EPT_DIR) ept_dir = Path(config.EPT_DIR)
if ept_dir.exists(): if ept_dir.exists():
for item in sorted(ept_dir.iterdir(), key=lambda x: x.stat().st_ctime, reverse=True): for item in sorted(ept_dir.iterdir(), key=lambda x: x.stat().st_ctime, reverse=True):
if item.is_dir(): if item.is_dir():
# Lire le manifeste
manifest_path = item / "manifest.json"
manifest = {}
if manifest_path.exists():
try:
manifest = {"ept_dir": item.name}
except:
pass
# Calculer la taille et le nombre de fichiers
total_size = 0 total_size = 0
file_count = 0 file_count = 0
for f in item.rglob("*"): for f in item.rglob("*"):
@ -42,12 +33,12 @@ async def viewer_list(request: Request):
"id": item.name, "id": item.name,
"size_mb": round(total_size / (1024 * 1024), 2), "size_mb": round(total_size / (1024 * 1024), 2),
"file_count": file_count, "file_count": file_count,
"manifest": manifest, "manifest": {},
"created": item.stat().st_ctime, "created": item.stat().st_ctime,
}) })
return request.app.state.templates.TemplateResponse( return request.app.state.templates.TemplateResponse(
"partials/cloud_list.html", "partials/cloud_list_body.html",
{"request": request, "pointclouds": pointclouds}, {"request": request, "pointclouds": pointclouds},
) )