diff --git a/app/main.py b/app/main.py index c0472e1..a41397d 100644 --- a/app/main.py +++ b/app/main.py @@ -121,6 +121,22 @@ async def api_model_usage(request: Request): return JSONResponse({"error": e.detail}, status_code=e.status_code) +@app.get("/api/metrics") +async def api_metrics(request: Request, days: int = 730): + """Daily metrics series from the portal (members + usage history). + + The portal owns this data (metrics_daily, refreshed 03:15 UTC); the panel + only reads it. Fetched once at the widest range and sliced client-side, so + the period toggle never refetches. + """ + if not is_admin(request): + return JSONResponse({"error": "forbidden"}, status_code=403) + try: + return await portal_get(f"/admin/metrics/series?days={max(1, min(int(days), 730))}") + except HTTPException as e: + return JSONResponse({"error": e.detail}, status_code=e.status_code) + + @app.post("/api/set-balance") async def api_set_balance(request: Request): if not is_admin(request): @@ -254,6 +270,18 @@ PAGE_HTML = """ .pill.inactive { background: #f6e9e5; color: var(--danger); } .pill.manual { background: #f1e8da; color: #a97a4a; } .pill.org { background: #e7edf5; color: #4a6a8a; } + /* Trends tab (charts). Colours come from the same vars as the rest of the + panel; the SVG is inline so there is no chart library and no CDN. */ + .trend-controls { display: flex; gap: 22px; flex-wrap: wrap; margin: 14px 0 0; } + .trend-controls .range-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } + .trend-card { background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px 12px; margin-top: 12px; } + .trend-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; } + .trend-title { font-weight: 700; font-size: 14px; color: var(--ink); } + .trend-sub { color: var(--muted); font-size: 12px; } + .trend-body svg { display: block; width: 100%; height: auto; overflow: visible; } + .trend-note { color: var(--muted); font-size: 12px; margin: 6px 0 0; } + .trend-empty { color: var(--muted); font-size: 13px; margin: 28px 0; text-align: center; } + @media (max-width: 600px) { .trend-controls { gap: 10px; } .trend-card { padding: 12px 10px; } } .num { font-variant-numeric: tabular-nums; } .remaining-col { font-weight: 700; color: var(--green-deep); background: #eaf3ea; } .flash { padding: 12px 16px; border-radius: 8px; margin: 16px 0; font-size: 14px; display: none; } @@ -306,6 +334,7 @@ PAGE_HTML = """

Members

+ @@ -337,6 +366,31 @@ PAGE_HTML = """ + +