diff --git a/app/main.py b/app/main.py index d1435af..2b9cbdd 100644 --- a/app/main.py +++ b/app/main.py @@ -2250,6 +2250,16 @@ async def admin_held_refund(hold_id: int, request: Request): return {"status": "refunded", "id": hold_id} +# Operator accounts — shown under a separate "admin" status in the panel and +# excluded from the member counts. They're infrastructure accounts (the SSO +# owner and the bot), not dues-paying members. +OPERATOR_EMAILS = { + e.strip().lower() + for e in os.environ.get("OPERATOR_EMAILS", "info@inference.coop,bot@inference.coop").split(",") + if e.strip() +} + + @app.get("/admin/overview") @limiter.limit("30/minute") async def admin_overview(request: Request): @@ -2326,42 +2336,52 @@ async def admin_overview(request: Request): logger.warning("admin overview: all-time spend query failed: %s", e) members = [] + admins = [] total_spend = 0.0 total_all_time = 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 member_all_time = all_time_spend.get(f"member:{email}", 0.0) - total_all_time += member_all_time credits = get_credit_balance(email) eff = compute_max_budget( float(balance) if balance is not None else MEMBER_BUDGET, credits, spend, ) - members.append( - { - "email": email, - "all_time_spend": round(member_all_time, 2), - "username": usernames.get(user_id), - "activated": invite_accepted.get(user_id, False), - "active": bool(active), - "manual": email.lower() in MANUAL_MEMBERS, - "balance": float(balance) if balance is not None else MEMBER_BUDGET, - "credit_balance": credits, - "credits_remaining": max(0.0, credits - eff["credits_used"]), - "allowance_used": eff["allowance_used"], - "credits_used": eff["credits_used"], - "spend": spend, - "remaining": max(eff["max_budget"] - spend, 0.0), - "reset_at": team.get("budget_reset_at"), - "cloudron_user_id": user_id, - } - ) + entry = { + "email": email, + "all_time_spend": round(member_all_time, 2), + "username": usernames.get(user_id), + "activated": invite_accepted.get(user_id, False), + "active": bool(active), + "manual": email.lower() in MANUAL_MEMBERS, + "balance": float(balance) if balance is not None else MEMBER_BUDGET, + "credit_balance": credits, + "credits_remaining": max(0.0, credits - eff["credits_used"]), + "allowance_used": eff["allowance_used"], + "credits_used": eff["credits_used"], + "spend": spend, + "remaining": max(eff["max_budget"] - spend, 0.0), + "reset_at": team.get("budget_reset_at"), + "cloudron_user_id": user_id, + } + if email.lower() in OPERATOR_EMAILS: + # Operator accounts: surfaced under a separate "admin" category in + # the panel, excluded from member counts and aggregates (their + # usage is a collective expense, not member activity). + entry["admin"] = True + admins.append(entry) + total_spend += spend + total_all_time += member_all_time + else: + members.append(entry) + total_spend += spend + total_all_time += member_all_time return { "members": members, + "admins": admins, "totals": { "members": len(members), "active": sum(1 for m in members if m["active"]),