Upload files to "frontend"
This commit is contained in:
parent
26088d26a2
commit
d0c29f3c50
3 changed files with 142 additions and 0 deletions
57
frontend/api_client.py
Normal file
57
frontend/api_client.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# api_client.py - httpx async
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from config import BACKEND_URL
|
||||||
|
|
||||||
|
TIMEOUT_HEALTH = 5
|
||||||
|
TIMEOUT_UPLOAD = 3600
|
||||||
|
TIMEOUT_DEFAULT = 15
|
||||||
|
|
||||||
|
|
||||||
|
async def check_health() -> dict:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(f"{BACKEND_URL}/health", timeout=TIMEOUT_HEALTH)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def upload_file(filename: str, data: bytes) -> dict:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(
|
||||||
|
f"{BACKEND_URL}/upload",
|
||||||
|
files={"file": (filename, data)},
|
||||||
|
timeout=TIMEOUT_UPLOAD,
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise RuntimeError(f"Erreur backend ({r.status_code}) : {r.text}")
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_debug(pc_id: str) -> dict:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(
|
||||||
|
f"{BACKEND_URL}/debug/{pc_id}", timeout=TIMEOUT_DEFAULT
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_pointcloud(pc_id: str) -> dict:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.delete(
|
||||||
|
f"{BACKEND_URL}/delete/{pc_id}", timeout=TIMEOUT_DEFAULT
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def crop_pointcloud(pc_id: str, payload: dict) -> dict:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(
|
||||||
|
f"{BACKEND_URL}/crop/{pc_id}",
|
||||||
|
json=payload,
|
||||||
|
timeout=TIMEOUT_UPLOAD,
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise RuntimeError(f"Erreur crop ({r.status_code}) : {r.text}")
|
||||||
|
return r.json()
|
||||||
31
frontend/config.py
Normal file
31
frontend/config.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# URL du backend - configurée uniquement via variable d'environnement BACKEND_URL
|
||||||
|
# Pour le développement local : BACKEND_URL=http://localhost:8091
|
||||||
|
# Pour la production : BACKEND_URL=http://backend_entwine:8000
|
||||||
|
|
||||||
|
# URL Potree - configurée via variable d'environnement POTREE_URL
|
||||||
|
# Pour le développement local : POTREE_URL=http://localhost:8090
|
||||||
|
# Pour la production : POTREE_URL=http://potree_server:8090
|
||||||
|
|
||||||
|
def load_backend_config():
|
||||||
|
"""Charge la configuration du backend depuis les variables d'environnement."""
|
||||||
|
return os.getenv(
|
||||||
|
"BACKEND_URL", "http://localhost:8091"
|
||||||
|
).strip().rstrip("/")
|
||||||
|
|
||||||
|
def load_potree_config():
|
||||||
|
"""Charge la configuration Potree depuis les variables d'environnement."""
|
||||||
|
return os.getenv(
|
||||||
|
"POTREE_URL", "http://localhost:8090"
|
||||||
|
).strip().rstrip("/")
|
||||||
|
|
||||||
|
BACKEND_URL = load_backend_config()
|
||||||
|
POTREE_URL = load_potree_config()
|
||||||
|
|
||||||
|
SUPPORTED_FORMATS = [".las", ".laz", ".ply", ".xyz", ".pts"]
|
||||||
|
SUPPORTED_EXTENSIONS = SUPPORTED_FORMATS # Alias pour compatibilité
|
||||||
|
|
||||||
|
# Chemin du dossier EPT (nuages de points convertis)
|
||||||
|
EPT_DIR = Path("/app/backend/data/ept")
|
||||||
54
frontend/main.py
Normal file
54
frontend/main.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# main.py - FastAPI + Jinja2
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from routes import upload, viewer, admin, crop
|
||||||
|
|
||||||
|
# -- Middleware ------------------------------------------------
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
from starlette.requests import Request
|
||||||
|
from starlette.responses import Response
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="PointCloud Frontend")
|
||||||
|
|
||||||
|
MAX_UPLOAD_BYTES = 10 * 1024 * 1024 * 1024 # 10 GB à ajuster
|
||||||
|
|
||||||
|
class LimitUploadSize(BaseHTTPMiddleware):
|
||||||
|
async def dispatch(self, request: Request, call_next):
|
||||||
|
if request.method == "POST":
|
||||||
|
content_length = request.headers.get("content-length")
|
||||||
|
if content_length and int(content_length) > MAX_UPLOAD_BYTES:
|
||||||
|
return Response(
|
||||||
|
content=f"Fichier trop volumineux. Maximum : {MAX_UPLOAD_BYTES // (1024**3)} GB",
|
||||||
|
status_code=413,
|
||||||
|
)
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
|
app.add_middleware(LimitUploadSize)
|
||||||
|
|
||||||
|
# ── Fichiers statiques ────────────────────────────────────────────────────────
|
||||||
|
app.mount(
|
||||||
|
"/static",
|
||||||
|
StaticFiles(directory=Path(__file__).parent / "static"),
|
||||||
|
name="static",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Jinja2 pour les pages et partials ────────────────────────────────────────
|
||||||
|
templates = Jinja2Templates(directory=Path(__file__).parent / "templates")
|
||||||
|
templates.env.filters["datetimeformat"] = lambda ts: (
|
||||||
|
datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%d %H:%M") if ts else "—"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rend les templates accessibles aux routes via app.state
|
||||||
|
app.state.templates = templates
|
||||||
|
|
||||||
|
# ── Routes ────────────────────────────────────────────────────────────────────
|
||||||
|
app.include_router(upload.router)
|
||||||
|
app.include_router(viewer.router)
|
||||||
|
app.include_router(admin.router)
|
||||||
|
app.include_router(crop.router)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue