From 12f01a17a9b720b104e6aa5b38022b3828abd7f3 Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 27 Aug 2026 17:22:14 -0400 Subject: [PATCH 1/2] =?UTF-8?q?frontend:=20Ghost-in-the-Shell=20overhaul?= =?UTF-8?q?=20=E2=80=94=20map-first=20landing,=20HUD=20chrome,=20event=20b?= =?UTF-8?q?lips,=20tickers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Map is now the landing view: full-viewport NASA GIBS globe, existing fires/cameras/HLS map machinery preserved verbatim - New 'Event Blips' layer: geolocated ingest events, color-coded by source, bbox/since/has_coords filters added to GET /api/events - Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending) - Market ticker strip with configurable symbols — auto-promotes to LIVE when GET /api/market returns {symbols:[...]} (contract documented in Settings) - Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle - Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings), Settings view (localStorage: symbols, map layer defaults), System panel - Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/ Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock - Fix: events.camera enum value missing from models.py/schemas.py caused 500s on every events query once camera events flowed in (migration 004 added it to the DB enum only) --- app/main.py | 37 + app/models.py | 2 +- app/schemas.py | 1 + app/static/index.html | 1822 ++++++++++++++++++++++++++++++----------- 4 files changed, 1386 insertions(+), 476 deletions(-) diff --git a/app/main.py b/app/main.py index 9d5f64d..d41f877 100644 --- a/app/main.py +++ b/app/main.py @@ -189,6 +189,21 @@ async def update_source(source_id: UUID, payload: dict): @app.get("/api/events", response_model=list[EventOut]) async def list_events( source_type: SourceType | None = Query(None), + bbox: str | None = Query( + None, + description="Comma-separated 'minlon,minlat,maxlon,maxlat' to bound the " + "result set by event coordinates. Omit for all stored events.", + ), + since: datetime | None = Query( + None, + description="Only events ingested at/after this UTC instant " + "(ISO 8601, e.g. '2026-08-24T12:00:00Z').", + ), + has_coords: bool = Query( + False, + description="Only events that carry a location (location_lat/lon set). " + "Used by the map's event-blips layer.", + ), limit: int = Query(50, ge=1, le=500), offset: int = Query(0, ge=0), ): @@ -197,6 +212,28 @@ async def list_events( stmt = select(events).order_by(events.c.ingested_at.desc()) if source_type: stmt = stmt.where(events.c.source_type == source_type.value) + if since: + stmt = stmt.where(events.c.ingested_at >= since) + if has_coords: + stmt = stmt.where(events.c.location_lat.isnot(None)) + if bbox: + parts = [p.strip() for p in bbox.split(",")] + if len(parts) != 4: + raise HTTPException( + 422, "bbox must be 'minlon,minlat,maxlon,maxlat' (4 comma-separated values)" + ) + try: + minlon, minlat, maxlon, maxlat = (float(p) for p in parts) + except ValueError: + raise HTTPException( + 422, "bbox values must be floats: 'minlon,minlat,maxlon,maxlat'" + ) + stmt = stmt.where( + and_( + events.c.location_lon >= minlon, events.c.location_lon <= maxlon, + events.c.location_lat >= minlat, events.c.location_lat <= maxlat, + ) + ) stmt = stmt.limit(limit).offset(offset) rows = (await session.execute(stmt)).mappings().all() return [event_to_out(r) for r in rows] diff --git a/app/models.py b/app/models.py index 4b332a0..706b5a2 100644 --- a/app/models.py +++ b/app/models.py @@ -39,7 +39,7 @@ events = Table( Column("id", UUID(as_uuid=True), primary_key=True, default=uuid4), Column("source_type", Enum( "rss", "gdel-t2", "social", "earthquake", "disaster", - "weather", "fire", "satellite", name="event_source_type" + "weather", "fire", "satellite", "camera", name="event_source_type" ), nullable=False, index=True), Column("source_id", UUID(as_uuid=True)), Column("title", Text), diff --git a/app/schemas.py b/app/schemas.py index 54d35c3..90d0f67 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -21,6 +21,7 @@ class SourceType(str, Enum): weather = "weather" fire = "fire" satellite = "satellite" + camera = "camera" class EntityKind(str, Enum): diff --git a/app/static/index.html b/app/static/index.html index 21f2088..986b34c 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -3,324 +3,612 @@ - OSINT Dashboard + OSINT//DASHBOARD — Global Situational Awareness Terminal + + + -
-

OSINT Dashboard

-
- Status: checking... -  |  Last update: - + + + + + +
+
+ ◢◤ +
+
OSINT//DASHBOARD
+
GLOBAL SITUATIONAL AWARENESS TERMINAL
+
+
+
+
+ + BOOT… + | + --:--:-- UTC + | + +
+
-
- -
-

Total Events

-
-

Events (24h)

-
last 24 hours
-

Active Sources

-
-

Open Alerts

-
-

Tracked Entities

-
-
-

Sentiment (24h)

-
-
-
-
-
-
-
-
+ +
- -
-

Search Events

- -
- - -
- - - - - - - -
- - -
-

Recent Events

- - - -
TimeSourceTitleSentimentLocation
-
- - - - - - - - - - - - - - -
+ + +
+
+
MKT
+
+
+
+
STANDBY
-
+
+
NEWS
+
+
+
+
15-MIN CYCLE
+
+ @@ -420,10 +952,321 @@ From 2a433a41e95e4ad973f8efc1a0b2d26d338f6a5c Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 27 Aug 2026 17:48:39 -0400 Subject: [PATCH 2/2] =?UTF-8?q?map:=20port=20master=20popup-guard=20fix=20?= =?UTF-8?q?=E2=80=94=20real=20popup=20state=20via=20events,=20close=20on?= =?UTF-8?q?=20zoom/drag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Master's b2369bc fixed cameras vanishing after opening a popup and zooming: Leaflet 1.9.4's Map.closePopup() never nulls map._popup, so the old 'if (map._popup) return' guard skipped every overlay reload forever after the first popup. Port the fix into the redesigned frontend: track popup state via popupopen/popupclose, close on user zoom/drag so moveend reloads always run, and keep the autopan skip for the popup's own pan. --- app/static/index.html | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/static/index.html b/app/static/index.html index 4c1efae..9afe27b 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -1621,11 +1621,22 @@ async function initMap() { if (activeHls) { try { activeHls.destroy(); } catch (_) {} activeHls = null; } }); updateHeatLegend(); - // Reload overlays when the user pans/zooms. Skip when a popup is open: - // opening a camera popup auto-pans the map to fit it, and that moveend - // must NOT rebuild the marker group under the open popup. + // Reload overlays when the user pans/zooms. A live popup makes the + // rebuild skip (so the popup's autopan doesn't destroy it), but user + // zoom/drag must close the popup FIRST — otherwise zooming out with a + // camera popup open leaves the stale, zoomed-in marker group on the + // map and the cameras "vanish" until a refresh. + // + // NOTE: `map._popup` is NOT a reliable open-check in Leaflet 1.9.4 — + // Map.closePopup() never nulls it, so once any popup has been opened + // it stays truthy forever. Track the real state via popupopen/close. + let camPopupOpen = false; + map.on('popupopen', () => { camPopupOpen = true; }); + map.on('popupclose', () => { camPopupOpen = false; }); + map.on('zoomstart', () => { if (camPopupOpen) map.closePopup(); }); + map.on('dragstart', () => { if (camPopupOpen) map.closePopup(); }); map.on('moveend', () => { - if (map._popup) return; + if (camPopupOpen) return; // only the popup's own autopan now if (firesOn) loadFires(); if (camsOn) loadCams(); if (blipsOn) loadBlips();