|
@@ -0,0 +1,178 @@
|
|
|
|
|
+"""
|
|
|
|
|
+Dashboard HTTP routes for astro-mcp.
|
|
|
|
|
+
|
|
|
|
|
+Serves Jinja2 templates for person management.
|
|
|
|
|
+All routes are prefixed with /dashboard.
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+from __future__ import annotations
|
|
|
|
|
+
|
|
|
|
|
+import logging
|
|
|
|
|
+from typing import Any
|
|
|
|
|
+
|
|
|
|
|
+from fastapi import APIRouter, Form, Request
|
|
|
|
|
+from fastapi.responses import HTMLResponse, RedirectResponse
|
|
|
|
|
+
|
|
|
|
|
+from . import storage
|
|
|
|
|
+from .server import templates
|
|
|
|
|
+
|
|
|
|
|
+logger = logging.getLogger("astro-mcp.dashboard")
|
|
|
|
|
+
|
|
|
|
|
+router = APIRouter()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── helpers ──────────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+async def _get_all_persons() -> list[dict[str, Any]]:
|
|
|
|
|
+ return await storage.list_persons()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+async def _get_one_person(person_id: str) -> dict[str, Any] | None:
|
|
|
|
|
+ person = await storage.get_person(person_id=person_id)
|
|
|
|
|
+ if not person:
|
|
|
|
|
+ person = await storage.get_person(nickname=person_id)
|
|
|
|
|
+ return person
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── dashboard home ───────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.get("/dashboard", response_class=HTMLResponse)
|
|
|
|
|
+async def dashboard_home(request: Request):
|
|
|
|
|
+ return templates.TemplateResponse(
|
|
|
|
|
+ request, "dashboard.html", {}
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── person list ──────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.get("/dashboard/persons", response_class=HTMLResponse)
|
|
|
|
|
+async def person_list(request: Request):
|
|
|
|
|
+ persons = await _get_all_persons()
|
|
|
|
|
+ return templates.TemplateResponse(
|
|
|
|
|
+ request, "persons.html",
|
|
|
|
|
+ {"persons": persons},
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── person detail ────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.get("/dashboard/persons/{person_id}", response_class=HTMLResponse)
|
|
|
|
|
+async def person_detail(request: Request, person_id: str):
|
|
|
|
|
+ person = await _get_one_person(person_id)
|
|
|
|
|
+ if not person:
|
|
|
|
|
+ return RedirectResponse("/dashboard/persons", status_code=303)
|
|
|
|
|
+ return templates.TemplateResponse(
|
|
|
|
|
+ request, "person-detail.html",
|
|
|
|
|
+ {"person": person},
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── add person (form) ────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.get("/dashboard/persons/new", response_class=HTMLResponse)
|
|
|
|
|
+async def person_add_form(request: Request):
|
|
|
|
|
+ return templates.TemplateResponse(
|
|
|
|
|
+ request, "person-form.html",
|
|
|
|
|
+ {"person": None, "action": "add"},
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── add person (submit) ──────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.post("/dashboard/persons", response_class=HTMLResponse)
|
|
|
|
|
+async def person_add_submit(
|
|
|
|
|
+ request: Request,
|
|
|
|
|
+ name: str = Form(...),
|
|
|
|
|
+ nickname: str = Form(default=""),
|
|
|
|
|
+ birth_datetime: str = Form(...),
|
|
|
|
|
+ birthplace: str = Form(default=""),
|
|
|
|
|
+ latitude: float = Form(...),
|
|
|
|
|
+ longitude: float = Form(...),
|
|
|
|
|
+ elevation: float = Form(default=0.0),
|
|
|
|
|
+ alive: bool = Form(default=True),
|
|
|
|
|
+ private: bool = Form(default=False),
|
|
|
|
|
+ gender: str = Form(default=""),
|
|
|
|
|
+ description: str = Form(default=""),
|
|
|
|
|
+ notes: str = Form(default=""),
|
|
|
|
|
+ tz: str = Form(default=""),
|
|
|
|
|
+ birth_time_known: bool = Form(default=True),
|
|
|
|
|
+):
|
|
|
|
|
+ await storage.add_person(
|
|
|
|
|
+ name=name,
|
|
|
|
|
+ birth_datetime=birth_datetime,
|
|
|
|
|
+ latitude=latitude,
|
|
|
|
|
+ longitude=longitude,
|
|
|
|
|
+ elevation=elevation,
|
|
|
|
|
+ nickname=nickname or None,
|
|
|
|
|
+ birthplace=birthplace or None,
|
|
|
|
|
+ alive=alive,
|
|
|
|
|
+ private=private,
|
|
|
|
|
+ gender=gender or None,
|
|
|
|
|
+ description=description or None,
|
|
|
|
|
+ notes=notes or None,
|
|
|
|
|
+ tz=tz or None,
|
|
|
|
|
+ birth_time_known=birth_time_known,
|
|
|
|
|
+ )
|
|
|
|
|
+ return RedirectResponse("/dashboard/persons", status_code=303)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── edit person (form) ───────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.get("/dashboard/persons/{person_id}/edit", response_class=HTMLResponse)
|
|
|
|
|
+async def person_edit_form(request: Request, person_id: str):
|
|
|
|
|
+ person = await _get_one_person(person_id)
|
|
|
|
|
+ if not person:
|
|
|
|
|
+ return RedirectResponse("/dashboard/persons", status_code=303)
|
|
|
|
|
+ return templates.TemplateResponse(
|
|
|
|
|
+ request, "person-form.html",
|
|
|
|
|
+ {"person": person, "action": "edit"},
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── edit person (submit) ─────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.post("/dashboard/persons/{person_id}", response_class=HTMLResponse)
|
|
|
|
|
+async def person_edit_submit(
|
|
|
|
|
+ request: Request,
|
|
|
|
|
+ person_id: str,
|
|
|
|
|
+ name: str = Form(...),
|
|
|
|
|
+ nickname: str = Form(default=""),
|
|
|
|
|
+ birth_datetime: str = Form(...),
|
|
|
|
|
+ birthplace: str = Form(default=""),
|
|
|
|
|
+ latitude: float = Form(...),
|
|
|
|
|
+ longitude: float = Form(...),
|
|
|
|
|
+ elevation: float = Form(default=0.0),
|
|
|
|
|
+ alive: bool = Form(default=True),
|
|
|
|
|
+ private: bool = Form(default=False),
|
|
|
|
|
+ gender: str = Form(default=""),
|
|
|
|
|
+ description: str = Form(default=""),
|
|
|
|
|
+ notes: str = Form(default=""),
|
|
|
|
|
+ tz: str = Form(default=""),
|
|
|
|
|
+ birth_time_known: bool = Form(default=True),
|
|
|
|
|
+):
|
|
|
|
|
+ await storage.update_person(
|
|
|
|
|
+ person_id=person_id,
|
|
|
|
|
+ name=name,
|
|
|
|
|
+ birth_datetime=birth_datetime,
|
|
|
|
|
+ latitude=latitude,
|
|
|
|
|
+ longitude=longitude,
|
|
|
|
|
+ elevation=elevation,
|
|
|
|
|
+ nickname=nickname or None,
|
|
|
|
|
+ birthplace=birthplace or None,
|
|
|
|
|
+ alive=alive,
|
|
|
|
|
+ private=private,
|
|
|
|
|
+ gender=gender or None,
|
|
|
|
|
+ description=description or None,
|
|
|
|
|
+ notes=notes or None,
|
|
|
|
|
+ tz=tz or None,
|
|
|
|
|
+ birth_time_known=birth_time_known,
|
|
|
|
|
+ )
|
|
|
|
|
+ return RedirectResponse(f"/dashboard/persons/{person_id}", status_code=303)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── delete person ────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+@router.post("/dashboard/persons/{person_id}/delete", response_class=HTMLResponse)
|
|
|
|
|
+async def person_delete(person_id: str):
|
|
|
|
|
+ await storage.delete_person(person_id)
|
|
|
|
|
+ return RedirectResponse("/dashboard/persons", status_code=303)
|