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 @@