yex
This commit is contained in:
@@ -9,7 +9,7 @@ from pydantic import ValidationError
|
||||
from devplacepy.seo import base_seo_context, site_url, website_schema
|
||||
from devplacepy.services.audit import record as audit
|
||||
from devplacepy.services.manager import service_manager
|
||||
from devplacepy.services.openai_gateway import routing
|
||||
from devplacepy.services.openai_gateway import quota, routing
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import require_admin
|
||||
|
||||
@@ -182,3 +182,92 @@ async def delete_model(request: Request, source_model: str):
|
||||
summary=f"admin {admin['username']} deleted gateway model route {source_model}",
|
||||
)
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
|
||||
def _quota_defaults_summary() -> dict:
|
||||
svc = service_manager.get_service("openai")
|
||||
cfg = svc.get_config() if svc is not None else {}
|
||||
return {
|
||||
"user": cfg.get(quota.FIELD_DEFAULT_USER, 0.0),
|
||||
"admin": cfg.get(quota.FIELD_DEFAULT_ADMIN, 0.0),
|
||||
"guest": cfg.get(quota.FIELD_DEFAULT_GUEST, 0.0),
|
||||
"internal": cfg.get(quota.FIELD_DEFAULT_INTERNAL, 0.0),
|
||||
"key": cfg.get(quota.FIELD_DEFAULT_KEY, 0.0),
|
||||
}
|
||||
|
||||
|
||||
def _rule_label(rule: dict) -> str:
|
||||
parts = []
|
||||
if rule.get("owner_kind"):
|
||||
parts.append(f"role={rule['owner_kind']}")
|
||||
if rule.get("owner_id"):
|
||||
parts.append(f"user={rule['owner_id']}")
|
||||
if rule.get("app_reference"):
|
||||
parts.append(f"app={rule['app_reference']}")
|
||||
return ", ".join(parts) or rule.get("uid", "")
|
||||
|
||||
|
||||
@router.get("/gateway/quota-rules")
|
||||
async def list_quota_rules(request: Request):
|
||||
require_admin(request)
|
||||
rules = quota.quota_rule_store.list()
|
||||
for rule in rules:
|
||||
rule["spent_24h_usd"] = round(
|
||||
quota.spent_24h(rule["owner_kind"], rule["owner_id"], rule["app_reference"]), 6
|
||||
)
|
||||
return JSONResponse(
|
||||
{
|
||||
"rules": rules,
|
||||
"count": len(rules),
|
||||
"defaults": _quota_defaults_summary(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post("/gateway/quota-rules")
|
||||
async def save_quota_rule(request: Request):
|
||||
admin = require_admin(request)
|
||||
body = await _payload(request)
|
||||
uid = str(body.pop("uid", "") or "").strip() or None
|
||||
try:
|
||||
payload = quota.QuotaRuleIn(**body)
|
||||
except ValidationError as exc:
|
||||
return _validation_error(exc)
|
||||
saved = quota.quota_rule_store.set(payload, uid=uid, created_by=admin["uid"])
|
||||
audit.record(
|
||||
request,
|
||||
"gateway.quota_rule.update",
|
||||
user=admin,
|
||||
target_type="gateway_quota_rule",
|
||||
target_uid=saved["uid"],
|
||||
target_label=_rule_label(saved),
|
||||
summary=f"admin {admin['username']} saved gateway quota rule ({_rule_label(saved)}) at ${saved['limit_usd']}/24h",
|
||||
metadata={
|
||||
"owner_kind": saved["owner_kind"],
|
||||
"owner_id": saved["owner_id"],
|
||||
"app_reference": saved["app_reference"],
|
||||
"limit_usd": saved["limit_usd"],
|
||||
"is_active": saved["is_active"],
|
||||
},
|
||||
)
|
||||
return JSONResponse({"ok": True, "rule": saved})
|
||||
|
||||
|
||||
@router.delete("/gateway/quota-rules/{uid}")
|
||||
async def delete_quota_rule(request: Request, uid: str):
|
||||
admin = require_admin(request)
|
||||
existing = quota.quota_rule_store.get(uid)
|
||||
label = _rule_label(existing.as_dict()) if existing else uid
|
||||
existed = quota.quota_rule_store.remove(uid)
|
||||
if not existed:
|
||||
return JSONResponse({"ok": False, "error": "Quota rule not found"}, status_code=404)
|
||||
audit.record(
|
||||
request,
|
||||
"gateway.quota_rule.delete",
|
||||
user=admin,
|
||||
target_type="gateway_quota_rule",
|
||||
target_uid=uid,
|
||||
target_label=label,
|
||||
summary=f"admin {admin['username']} deleted gateway quota rule ({label})",
|
||||
)
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
@@ -4,6 +4,7 @@ import logging
|
||||
|
||||
from devplacepy.database import get_correction_usage, get_modifier_usage
|
||||
from devplacepy.services.manager import service_manager
|
||||
from devplacepy.services.openai_gateway import quota as gateway_quota
|
||||
from devplacepy.services.openai_gateway.analytics import user_spend_24h
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -62,4 +63,22 @@ def _ai_quota(
|
||||
if include_cost:
|
||||
quota["spent_usd"] = round(spent, 4)
|
||||
quota["limit_usd"] = round(limit, 2)
|
||||
gateway_svc = service_manager.get_service("openai")
|
||||
if gateway_svc is not None:
|
||||
try:
|
||||
owner_kind = "admin" if is_admin else "user"
|
||||
cfg = gateway_svc.effective_config()
|
||||
gw_limit, gw_scope, gw_rule = gateway_quota.resolve_for_owner(owner_kind, user_uid, cfg)
|
||||
gw_spent = gateway_quota.spent_24h(*gw_scope)
|
||||
gw_unlimited = gw_limit <= 0
|
||||
quota["gateway_unlimited"] = gw_unlimited
|
||||
quota["gateway_used_pct"] = (
|
||||
0.0 if gw_unlimited else round(min(100.0, gw_spent / gw_limit * 100), 1)
|
||||
)
|
||||
if include_cost:
|
||||
quota["gateway_spent_usd"] = round(gw_spent, 4)
|
||||
quota["gateway_limit_usd"] = round(gw_limit, 2)
|
||||
quota["gateway_pooled"] = bool(gw_rule and gw_rule.owner_id is None)
|
||||
except Exception:
|
||||
logger.exception("Failed to compute gateway-level AI quota for %s", user_uid)
|
||||
return quota
|
||||
|
||||
Reference in New Issue
Block a user