diff --git a/src/central/gui/routes.py b/src/central/gui/routes.py index 74a6d6b..7673e21 100644 --- a/src/central/gui/routes.py +++ b/src/central/gui/routes.py @@ -1,11 +1,10 @@ """Route handlers for Central GUI.""" import base64 -import html import json import logging import re -from datetime import datetime, timezone +from datetime import datetime from typing import Any logger = logging.getLogger("central.gui.routes") @@ -2728,24 +2727,6 @@ def _parse_events_params(params) -> tuple[dict | None, str | None]: }, None -def _derive_subject(event: dict) -> str | None: - """Derive an event's plain-text subject for the JSON API. - - Renders the same per-adapter ``_event_summaries/{adapter}.html`` partial - the /events table uses (falling back to ``_default.html``), so the JSON - subject carries the same human text as the GUI's Subject cell with no - duplicated derivation logic. The partials are HTML-autoescaped for the - table (e.g. ``>`` -> ``>``); we ``html.unescape`` so JSON consumers get - plain text. Returns ``None`` when the partial yields no text -- an unknown - adapter, or an event whose source fields don't support a subject (e.g. a - wfigs row with neither county nor state). - """ - template = _get_templates().env.select_template( - [f"_event_summaries/{event.get('adapter')}.html", "_event_summaries/_default.html"] - ) - return html.unescape(template.render(event=event)).strip() or None - - async def _fetch_events(parsed_params: dict) -> EventsQueryResult: """ Fetch events from database using parsed parameters. @@ -2813,6 +2794,7 @@ async def _fetch_events(parsed_params: dict) -> EventsQueryResult: received, adapter, category, + payload->>'subject' as subject, ST_AsGeoJSON(geom) as geometry, payload as data, regions @@ -2842,23 +2824,17 @@ async def _fetch_events(parsed_params: dict) -> EventsQueryResult: if row["geometry"]: geometry = json.loads(row["geometry"]) - event = { + events.append({ "id": row["id"], "time": row["time"].isoformat(), "received": row["received"].isoformat(), "adapter": row["adapter"], "category": row["category"], + "subject": row["subject"], "geometry": geometry, "data": dict(row["data"]) if row["data"] else {}, "regions": list(row["regions"]) if row["regions"] else [], - } - # Subject is derived from the inner adapter payload by rendering the - # same _event_summaries partial the /events table uses, so the JSON - # `subject` matches the GUI's Subject cell. (The CloudEvents envelope - # has no top-level `subject`; the old `payload->>'subject'` was always - # null for every consumer.) - event["subject"] = _derive_subject(event) - events.append(event) + }) # Build next_cursor if there are more results next_cursor = None @@ -2894,31 +2870,6 @@ def _geometry_summary(geometry: dict | None) -> str: return geom_type -def _format_event_time(iso: str | None) -> str: - """Format an ISO-8601 timestamp as 'MM-DD-YYYY HH:MM UTC' (24h, no seconds).""" - if not iso: - return "" - try: - dt = datetime.fromisoformat(iso).astimezone(timezone.utc) - except (ValueError, TypeError): - return iso - return dt.strftime("%m-%d-%Y %H:%M") + " UTC" - - -def _decorate_table_events(events: list[dict]) -> None: - """Add display-only fields used by the HTML events table (in place). - - These are for the table chrome only and are deliberately NOT added in - _fetch_events, so the /events.json payload is unchanged. adapter_display - is sourced from the registry (display_name), with the bare name as fallback. - """ - display = {cls.name: cls.display_name for cls in discover_adapters().values()} - for event in events: - event["geometry_summary"] = _geometry_summary(event.get("geometry")) - event["time_human"] = _format_event_time(event.get("time")) - event["adapter_display"] = display.get(event.get("adapter"), event.get("adapter")) - - @router.get("/events.json") async def events_json(request: Request): @@ -3007,8 +2958,9 @@ async def events_list(request: Request) -> HTMLResponse: events = result.events next_cursor = result.next_cursor - # Add table-only display fields (time_human, adapter_display, geometry_summary) - _decorate_table_events(events) + # Add geometry summary to each event + for event in events: + event["geometry_summary"] = _geometry_summary(event.get("geometry")) # Registry-derived adapter list for the filter