Add /admin/overview endpoint (member list + spend + co-op aggregates)
This commit is contained in:
1 parent
841eb70e4d
commit
9da67a9021
1 file changed
+59
+59
@@ -1080,6 +1080,65 @@ async def admin_migrate_teams(token: str):
|
|||||||
return {"status": "migrated", "results": results}
|
return {"status": "migrated", "results": results}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/admin/overview/{token}")
|
||||||
|
async def admin_overview(token: str):
|
||||||
|
"""Return a co-op-wide overview for the admin dashboard.
|
||||||
|
|
||||||
|
Protected by the same secret token as the other /admin endpoints. Returns
|
||||||
|
every member (active + inactive) with their balance, spend, remaining, and
|
||||||
|
team reset date, plus co-op aggregates (total members, total spend).
|
||||||
|
"""
|
||||||
|
if not WEBHOOK_TOKEN or not secrets.compare_digest(token, WEBHOOK_TOKEN):
|
||||||
|
raise HTTPException(401, "Invalid token")
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT email, active, balance, cloudron_user_id FROM members ORDER BY email"
|
||||||
|
).fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Fetch team spend/budget from LiteLLM once.
|
||||||
|
teams = {}
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||||
|
r = await client.get(
|
||||||
|
f"{LITELLM_BASE}/team/list",
|
||||||
|
headers={"Authorization": f"Bearer {LITELLM_MASTER_KEY}"},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
for t in r.json():
|
||||||
|
teams[t.get("team_alias")] = t
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("admin overview: team/list failed: %s", e)
|
||||||
|
|
||||||
|
members = []
|
||||||
|
total_spend = 0.0
|
||||||
|
for email, active, balance, user_id in rows:
|
||||||
|
team = teams.get(f"member:{email}", {})
|
||||||
|
spend = float(team.get("spend") or 0.0)
|
||||||
|
total_spend += spend
|
||||||
|
members.append(
|
||||||
|
{
|
||||||
|
"email": email,
|
||||||
|
"active": bool(active),
|
||||||
|
"balance": float(balance) if balance is not None else MEMBER_BUDGET,
|
||||||
|
"spend": spend,
|
||||||
|
"remaining": max((balance or MEMBER_BUDGET) - spend, 0.0),
|
||||||
|
"reset_at": team.get("budget_reset_at"),
|
||||||
|
"cloudron_user_id": user_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"members": members,
|
||||||
|
"totals": {
|
||||||
|
"members": len(members),
|
||||||
|
"active": sum(1 for m in members if m["active"]),
|
||||||
|
"total_spend": total_spend,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def litellm_delete_keys_by_alias(email: str) -> None:
|
async def litellm_delete_keys_by_alias(email: str) -> None:
|
||||||
"""Delete all LiteLLM keys whose alias matches member:<email>.
|
"""Delete all LiteLLM keys whose alias matches member:<email>.
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user