Add system alerts log with nav badge and Admin Alerts tab.
Persist job and ingestion warnings/errors for 7 days, surface a dismissible top-nav badge, treat stale OHLCV as a warning (e.g. ticker renames), and show market bar age on the ticker freshness chip.
This commit is contained in:
+49
-1
@@ -6,7 +6,7 @@ All endpoints require admin role.
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dependencies import get_db, require_admin
|
||||
from app.dependencies import get_db, require_access, require_admin
|
||||
from app.models.user import User
|
||||
from app.schemas.admin import (
|
||||
ActivationConfigUpdate,
|
||||
@@ -29,6 +29,7 @@ from app.schemas.common import APIEnvelope
|
||||
from app.services import admin_service
|
||||
from app.services import alert_service
|
||||
from app.services import sentiment_provider_service
|
||||
from app.services import system_event_service
|
||||
from app.services import ticker_universe_service
|
||||
|
||||
router = APIRouter(tags=["admin"])
|
||||
@@ -403,3 +404,50 @@ async def toggle_job(
|
||||
status="success",
|
||||
data={"key": setting.key, "value": setting.value},
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# System events (operational warnings / errors)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.get("/admin/system-events", response_model=APIEnvelope)
|
||||
async def list_system_events(
|
||||
days: int = Query(7, ge=1, le=30),
|
||||
severity: str | None = Query(None, description="warning | error"),
|
||||
unacknowledged_only: bool = Query(False),
|
||||
_user: User = Depends(require_access),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List recent system events (default last 7 days)."""
|
||||
rows = await system_event_service.list_events(
|
||||
db,
|
||||
days=days,
|
||||
severity=severity,
|
||||
unacknowledged_only=unacknowledged_only,
|
||||
)
|
||||
return APIEnvelope(
|
||||
status="success",
|
||||
data=[system_event_service.event_to_dict(r) for r in rows],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/admin/system-events/summary", response_model=APIEnvelope)
|
||||
async def system_events_summary(
|
||||
days: int = Query(7, ge=1, le=30),
|
||||
_user: User = Depends(require_access),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Badge counts for the top nav."""
|
||||
data = await system_event_service.summary(db, days=days)
|
||||
return APIEnvelope(status="success", data=data)
|
||||
|
||||
|
||||
@router.post("/admin/system-events/acknowledge", response_model=APIEnvelope)
|
||||
async def acknowledge_system_events(
|
||||
days: int = Query(7, ge=1, le=30),
|
||||
_user: User = Depends(require_access),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Dismiss unacknowledged events in the lookback window (clears the badge)."""
|
||||
count = await system_event_service.acknowledge_all(db, days=days)
|
||||
return APIEnvelope(status="success", data={"acknowledged": count})
|
||||
|
||||
@@ -117,12 +117,32 @@ async def fetch_symbol(
|
||||
result = await ingestion_service.fetch_and_ingest(
|
||||
db, provider, symbol_upper, start_date, end_date
|
||||
)
|
||||
status_map = {"complete": "ok", "partial": "ok", "no_data": "warning"}
|
||||
# "stale" = provider returned nothing but our last bar is old
|
||||
# (rename/delist/halt) — must not look like a successful refresh.
|
||||
status_map = {
|
||||
"complete": "ok",
|
||||
"partial": "ok",
|
||||
"no_data": "warning",
|
||||
"stale": "warning",
|
||||
}
|
||||
sources_out["ohlcv"] = {
|
||||
"status": status_map.get(result.status, "error"),
|
||||
"records": result.records_ingested,
|
||||
"message": result.message,
|
||||
"last_date": result.last_date.isoformat() if result.last_date else None,
|
||||
}
|
||||
if result.status in ("stale", "no_data", "error"):
|
||||
from app.services.system_event_service import log_event
|
||||
|
||||
await log_event(
|
||||
db,
|
||||
severity="warning" if result.status != "error" else "error",
|
||||
source="ingestion",
|
||||
code=f"ohlcv_{result.status}",
|
||||
message=result.message or f"OHLCV fetch {result.status} for {symbol_upper}",
|
||||
symbol=symbol_upper,
|
||||
dedup_key=f"ohlcv_{result.status}:{symbol_upper}",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error("OHLCV fetch failed for %s: %s", symbol_upper, exc)
|
||||
sources_out["ohlcv"] = {"status": "error", "records": 0, "message": str(exc)}
|
||||
|
||||
Reference in New Issue
Block a user