diff --git a/README.md b/README.md index d3041b4..a89fcf1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,19 @@ celery -A celery_app worker --loglevel=info python server.py ``` -## L1 Server API +## Web UI + +The server provides a web interface at the root URL: + +| Path | Description | +|------|-------------| +| `/` | Home page with server info | +| `/runs` | View and manage rendering runs | +| `/recipes` | Browse and run available recipes | +| `/media` | Browse cached media files | +| `/run/{id}` | Run detail page | + +## API Interactive docs: http://localhost:8100/docs diff --git a/server.py b/server.py index 253b21a..3def56f 100644 --- a/server.py +++ b/server.py @@ -129,6 +129,57 @@ app = FastAPI( ) +@app.exception_handler(404) +async def not_found_handler(request: Request, exc): + """Custom 404 page.""" + from fastapi.responses import JSONResponse + accept = request.headers.get("accept", "") + if "text/html" in accept: + content = ''' +
+

404

+

Page not found

+ Go to home page +
+ ''' + # Import render_page at runtime to avoid circular dependency + html = f""" + + + + + Not Found | Art DAG L1 Server + + + + +
+
+

+ Art DAG L1 Server +

+
+
+ {content} +
+
+ +""" + return HTMLResponse(html, status_code=404) + return JSONResponse({"detail": "Not found"}, status_code=404) + + class RunRequest(BaseModel): """Request to start a run.""" recipe: str # Recipe name (e.g., "dog", "identity") or "dag" for custom DAG @@ -1645,7 +1696,7 @@ async def ui_recipes_list(request: Request): ctx = await get_user_context_from_cookie(request) if not ctx: - return '

Login via L2 to see recipes.

' + return '

Login via L2 to see recipes.

' all_recipes = list_all_recipes() @@ -3595,31 +3646,6 @@ async def logout(): return response -@app.get("/ui") -async def ui_index(tab: str = "runs"): - """Redirect /ui to clean URLs.""" - if tab == "cache": - return RedirectResponse(url="/media", status_code=302) - return RedirectResponse(url="/runs", status_code=302) - - -@app.get("/ui/login") -async def ui_login_page(): - """Redirect to L2 login.""" - return RedirectResponse(url="/login", status_code=302) - - -@app.get("/ui/register") -async def ui_register_page(): - """Redirect to L2 register.""" - return RedirectResponse(url="/register", status_code=302) - - -@app.get("/ui/logout") -async def ui_logout(): - """Redirect to logout.""" - return RedirectResponse(url="/logout", status_code=302) - @app.post("/ui/publish-run/{run_id}", response_class=HTMLResponse) async def ui_publish_run(run_id: str, request: Request, output_name: str = Form(...)): @@ -3712,7 +3738,7 @@ async def ui_runs(request: Request): # Require login to see runs if not ctx: - return '

Login via L2 to see your runs.

' + return '

Login via L2 to see your runs.

' # Filter runs by user - match both plain username and ActivityPub format (@user@domain) runs = [r for r in runs if r.username in (ctx.username, ctx.actor_id)]