Spaces:
Sleeping
Sleeping
File size: 6,763 Bytes
e8aac63 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | """Restricted management. Roles are granted using the server CLI, never public signup."""
from typing import Annotated
from fastapi import APIRouter, Depends, Query, Request
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy import delete, func, select
from .auth import DB, CurrentUser, user_view
from .errors import fail
from .models import (
AuditLog,
Provider,
PublishedModel,
RegistryModel,
RequestLog,
Session,
SiteConfiguration,
User,
)
from .site_config import SiteOptions, site_options
router = APIRouter(prefix="/api", tags=["Administration"])
async def administrator(user: CurrentUser) -> User:
if not user.is_admin:
raise fail(403, "Administrator access is required.", "admin_required")
return user
AdminUser = Annotated[User, Depends(administrator)]
@router.get("/site")
async def public_settings(request: Request, db: DB) -> dict:
options = await site_options(db, request.app.state.settings)
return options.model_dump(
include={
"site_name",
"tagline",
"welcome_text",
"accent",
"default_mode",
"default_stream",
"default_retries",
"shared_models_enabled",
}
)
@router.get("/admin/settings")
async def get_settings(request: Request, db: DB, user: AdminUser) -> dict:
options = await site_options(db, request.app.state.settings)
return {
"settings": options.model_dump(),
"limits": {
"requests_per_minute": request.app.state.settings.requests_per_minute,
"max_retries": request.app.state.settings.max_retries,
"registration_allowed": request.app.state.settings.allow_registration,
},
}
@router.put("/admin/settings")
async def save_settings(body: SiteOptions, request: Request, db: DB, user: AdminUser) -> dict:
config = request.app.state.settings
if (
body.requests_per_minute > config.requests_per_minute
or body.default_retries > config.max_retries
):
raise fail(422, "Keep rate and retry limits within the server's configured maximums.")
if body.registration_open and not config.allow_registration:
raise fail(422, "Registration is disabled in the server configuration.")
row = await db.get(SiteConfiguration, "global")
if not row:
row = SiteConfiguration(id="global")
db.add(row)
row.settings = body.model_dump()
db.add(AuditLog(user_id=user.id, action="admin.settings.updated", target_id="global"))
await db.commit()
return body.model_dump()
@router.get("/admin/summary")
async def summary(db: DB, user: AdminUser) -> dict:
return {
"users": await db.scalar(select(func.count(User.id))),
"suspended_users": await db.scalar(
select(func.count(User.id)).where(User.disabled.is_(True))
),
"requests": await db.scalar(select(func.count(RequestLog.id))),
"published_models": await db.scalar(
select(func.count(PublishedModel.model_id)).where(PublishedModel.enabled.is_(True))
),
"sponsored_cost": await db.scalar(select(func.sum(RequestLog.sponsored_cost))),
}
@router.get("/admin/users")
async def users(db: DB, user: AdminUser, search: str = "", offset: int = Query(0, ge=0)) -> dict:
query = select(User)
if search:
query = query.where(User.email.icontains(search[:120], autoescape=True))
total = await db.scalar(select(func.count()).select_from(query.subquery()))
rows = (
await db.scalars(query.order_by(User.created_at, User.id).limit(50).offset(offset))
).all()
return {
"data": [user_view(u) | {"disabled": u.disabled, "created_at": u.created_at} for u in rows],
"total": total,
}
class AccountStatus(BaseModel):
model_config = ConfigDict(extra="forbid")
disabled: bool
@router.patch("/admin/users/{uid}")
async def account_status(uid: str, body: AccountStatus, db: DB, user: AdminUser) -> dict:
target = await db.get(User, uid)
if not target:
raise fail(404, "Account not found.")
if target.id == user.id or target.is_admin:
raise fail(
403, "Administrator accounts can only be changed using the server admin command."
)
target.disabled = body.disabled
if body.disabled:
await db.execute(delete(Session).where(Session.user_id == target.id))
db.add(
AuditLog(
user_id=user.id,
action="admin.user.suspended" if body.disabled else "admin.user.restored",
target_id=uid,
)
)
await db.commit()
return {"id": uid, "disabled": target.disabled}
class PublishOptions(BaseModel):
model_config = ConfigDict(extra="forbid")
enabled: bool = True
requests_per_minute: int = Field(default=5, ge=1, le=120)
max_output_tokens: int = Field(default=1024, ge=128, le=8192)
@router.get("/admin/shared-models")
async def shared_models(db: DB, user: AdminUser) -> list[dict]:
rows = (
await db.execute(
select(PublishedModel, RegistryModel, Provider)
.join(RegistryModel, RegistryModel.id == PublishedModel.model_id)
.join(Provider, Provider.id == RegistryModel.provider_id)
.order_by(RegistryModel.name)
)
).all()
return [
{
"id": m.id,
"name": m.name,
"model_id": m.model_id,
"provider_name": p.name,
"enabled": shared.enabled,
"requests_per_minute": shared.requests_per_minute,
"max_output_tokens": shared.max_output_tokens,
"owned": p.user_id == user.id,
}
for shared, m, p in rows
]
@router.put("/admin/shared-models/{mid}")
async def publish(mid: str, body: PublishOptions, db: DB, user: AdminUser) -> dict:
model = await db.scalar(
select(RegistryModel)
.join(Provider)
.where(
RegistryModel.id == mid,
Provider.user_id == user.id,
)
)
if not model:
raise fail(404, "Select a model from a provider you own.")
if model.capabilities.get("chat") is not True:
raise fail(422, "Shared models must have confirmed chat capability.")
row = await db.get(PublishedModel, mid)
if not row:
row = PublishedModel(model_id=mid)
db.add(row)
for key, value in body.model_dump().items():
setattr(row, key, value)
db.add(
AuditLog(
user_id=user.id,
action="admin.model.published" if body.enabled else "admin.model.unpublished",
target_id=mid,
)
)
await db.commit()
return {"id": mid, **body.model_dump()}
|