engine: remove dead UI/stats/data handlers now owned by the front

With --single-process gone, the engine only ever runs behind the front, which
serves these before its catch-all — so the engine copies were dead code. Removed
from codai/admin/routes.py:
  - api_status (GET /admin/api/status) — front builds status from its own state
  - api_list_tokens/api_create_token/api_delete_token — front admin_data (auth.json)
  - api_create_user/api_delete_user — front admin_data (auth.json)
  - api_get_settings (GET) — front serves via the shared build_settings_dict

Kept: build_settings_dict (imported by the front), api_save_settings (POST — the
engine still persists/applies config), api_list_models, and the auth POSTs
(login/logout/change-password) the front still proxies.

The engine now does generation + model management only; the front owns pages,
stats and these data reads. Verified routes.py imports and compiles.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DDv7BchtZQWsnPG6Jm49m
parent 7b564eae
...@@ -403,348 +403,10 @@ async def chat_page(request: Request, username: str = Depends(require_auth)): ...@@ -403,348 +403,10 @@ async def chat_page(request: Request, username: str = Depends(require_auth)):
# API endpoints for admin operations # API endpoints for admin operations
@router.get("/admin/api/status", summary="Server and model status") # NOTE: /admin/api/status, /admin/api/tokens (GET/POST/DELETE) and
def api_status(username: str = Depends(require_auth)): # /admin/api/users (POST/DELETE) are served by the FRONT now
"""Get system status. # (codai/frontproxy/admin_data.py + FrontProxy.status); the engine only
# generates and never serves these. Removed from here.
Defined as a SYNC handler on purpose: it reads VRAM via sysfs / a blocking
``lspci`` subprocess (AMD/Intel) and scans registry/queue state. The Tasks
and dashboard pages poll it continuously, so running it on the event loop
would freeze the whole web UI while a model loads (llama.cpp's load releases
the GIL, so a threadpool worker can still run). FastAPI runs plain ``def``
path operations in its threadpool, off the event loop."""
from codai.models.manager import multi_model_manager
from codai.api.state import get_load_mode
loaded_keys = list(multi_model_manager.models.keys())
# Real-ESRGAN / diffusers upscalers live in a private cache (deliberately not
# in multi_model_manager.models — see codai/api/images.py), so the registry
# count alone reports 0 models while an upscale job is actively running. Fold
# the loaded upscalers in so the dashboard count matches the Tasks page.
try:
from codai.api.images import _UPSCALER_CACHE as _upscalers
for _uk in _upscalers.keys():
if _uk not in loaded_keys:
loaded_keys.append(_uk)
except Exception:
pass
# Whisper-server models run as their own subprocess (not in .models); fold each
# running server in (id + `audio:` alias) so the dashboard/engine count and the
# models page reflect them — including on a secondary engine.
try:
for _wid, _wsm in multi_model_manager.whisper_servers.items():
if _wsm.is_running():
_mp = getattr(_wsm, "_model_path", None)
for _wk in (_wid, f"audio:{_wid}", _mp):
if _wk and _wk not in loaded_keys:
loaded_keys.append(_wk)
except Exception:
pass
# VRAM info
vram = None
is_cuda = False
try:
import torch
if torch.cuda.is_available():
is_cuda = True
free, total = torch.cuda.mem_get_info()
used = total - free
vram = {"used": round(used / 1e9, 2), "free": round(free / 1e9, 2), "total": round(total / 1e9, 2),
"gpu": torch.cuda.get_device_name(0)}
except Exception:
pass
# Non-CUDA: read from sysfs (AMD amdgpu / Intel i915 / Arc)
if not is_cuda:
import os, glob as _glob
for card in sorted(_glob.glob("/sys/class/drm/card[0-9]")):
dev = card + "/device"
vram_total_path = dev + "/mem_info_vram_total"
if not os.path.exists(vram_total_path):
continue
try:
total_b = int(open(vram_total_path).read())
used_b = int(open(dev + "/mem_info_vram_used").read())
free_b = total_b - used_b
# GPU name from lspci
gpu_name = ""
try:
pci_addr = os.path.basename(os.path.realpath(dev))
import subprocess
r = subprocess.run(["lspci", "-s", pci_addr], capture_output=True, text=True, timeout=3)
if r.returncode == 0 and r.stdout:
# "05:00.0 VGA compatible controller: AMD Radeon RX 580"
gpu_name = r.stdout.split(":", 2)[-1].strip().rstrip()
except Exception:
pass
vram = {
"gpu": gpu_name,
"used": round(used_b / 1e9, 2),
"free": round(free_b / 1e9, 2),
"total": round(total_b / 1e9, 2),
}
break
except Exception:
continue
# Request stats from queue manager
req_total = 0
req_active = 0
req_waiting = 0
req_metrics = {
"max_parallel_requests": 0,
"queue_max_size": 0,
"active_by_model": {},
"waiting_by_model": {},
}
try:
from codai.queue.manager import queue_manager
metrics = queue_manager.get_metrics()
req_active = int(metrics.get("active", 0))
req_waiting = int(metrics.get("waiting", 0))
req_total = req_active + req_waiting
req_metrics = {
"max_parallel_requests": metrics.get("max_parallel_requests", 0),
"queue_max_size": metrics.get("queue_max_size", 0),
"active_by_model": metrics.get("active_by_model", {}),
"waiting_by_model": metrics.get("waiting_by_model", {}),
}
except Exception:
pass
# Backend info
backend = "—"
try:
from codai.models.manager import model_manager
if model_manager.backend_type:
backend = model_manager.backend_type
except Exception:
pass
# Enabled (configured) models + aliases
enabled_models = []
enabled_aliases: dict = {} # alias -> [model_id, ...]
try:
if config_manager:
md = config_manager.models_data
for cat in ("text_models", "image_models", "audio_models", "vision_models", "tts_models",
"video_models", "audio_gen_models", "embedding_models", "spatial_models"):
for m in md.get(cat, []):
if isinstance(m, dict):
mid = m.get("path") or m.get("id") or ""
alias = (m.get("alias") or "").strip()
else:
mid = m
alias = ""
if mid and mid not in enabled_models:
enabled_models.append(mid)
if alias:
enabled_aliases.setdefault(alias, [])
if mid and mid not in enabled_aliases[alias]:
enabled_aliases[alias].append(mid)
except Exception:
pass
# Recent activity
recent_activity = []
try:
from codai.api.log import get_recent_activity
for row in get_recent_activity():
if not isinstance(row, dict):
continue
try:
ts = int(row.get("time", 0) or 0)
except (TypeError, ValueError):
ts = 0
try:
status = int(row.get("status", 0) or 0)
except (TypeError, ValueError):
status = 0
try:
duration = round(float(row.get("duration", 0) or 0), 2)
except (TypeError, ValueError):
duration = 0.0
recent_activity.append({
"time": ts,
"model": str(row.get("model") or "—"),
"type": str(row.get("type") or "unknown"),
"status": status,
"duration": duration,
})
except Exception:
pass
# Host-RAM status: system totals (psutil) + the global-cap watcher snapshot
# (process RSS, configured cap, % of cap, leak-suspected flag, last mitigation).
ram = None
try:
import psutil
vm = psutil.virtual_memory()
ram = {
"total": round(vm.total / 1e9, 2),
"used": round((vm.total - vm.available) / 1e9, 2),
"free": round(vm.available / 1e9, 2),
"percent": vm.percent,
}
try:
from codai.models.ram_monitor import get_status as _ram_status
ram["watch"] = _ram_status()
except Exception:
pass
except Exception:
pass
# Whisper-server status
whisper_status = None
try:
from codai.models.manager import multi_model_manager as _mmm
if _mmm.whisper_servers:
whisper_status = {mid: wsm.get_status() for mid, wsm in _mmm.whisper_servers.items()}
elif _mmm.whisper_server:
whisper_status = {"whisper-server": _mmm.whisper_server.get_status()}
except Exception:
pass
return {
"status": "ok",
"backend": backend,
"load_mode": get_load_mode(),
"models_loaded": len(loaded_keys),
"loaded_models": loaded_keys,
"enabled_models": enabled_models,
"enabled_aliases": enabled_aliases,
"vram": vram,
"ram": ram,
"cuda": is_cuda,
"requests": {
"total": req_total,
"active": req_active,
"waiting": req_waiting,
"max_parallel_requests": req_metrics["max_parallel_requests"],
"queue_max_size": req_metrics["queue_max_size"],
"active_by_model": req_metrics["active_by_model"],
"waiting_by_model": req_metrics["waiting_by_model"],
},
"recent_activity": recent_activity,
"whisper_server": whisper_status,
}
@router.post("/admin/api/users", summary="Create a user")
async def api_create_user(
request: Request,
username: str = Depends(require_admin)
):
"""Create a new user."""
data = await request.json()
new_username = data.get("username")
password = data.get("password")
role = data.get("role", "user")
if not new_username or not password:
raise HTTPException(status_code=400, detail="Username and password required")
success = session_manager.create_user(new_username, password, role)
if not success:
raise HTTPException(status_code=400, detail="User already exists")
return {"success": True}
@router.delete("/admin/api/users/{user_id}", summary="Delete a user")
async def api_delete_user(
user_id: int,
username: str = Depends(require_admin)
):
"""Delete a user."""
users = session_manager._load_auth_data().get("users", [])
user = next((u for u in users if u["id"] == user_id), None)
if not user:
raise HTTPException(status_code=404, detail="User not found")
success = session_manager.delete_user(user["username"])
if not success:
raise HTTPException(status_code=400, detail="Cannot delete user")
return {"success": True}
# --- Token management endpoints ---
@router.get("/admin/api/tokens", response_model=list, summary="List API tokens")
async def api_list_tokens(username: str = Depends(require_admin)):
"""List all API tokens."""
auth_data = session_manager._load_auth_data()
tokens = []
for token in auth_data.get("tokens", []):
tokens.append({
"id": token["id"],
"name": token["name"],
"token": token["token"],
"provider": token["provider"],
"created_at": token["created_at"],
"last_used": token.get("last_used")
})
return tokens
@router.post("/admin/api/tokens", summary="Create an API token")
async def api_create_token(request: Request, username: str = Depends(require_admin)):
"""Create a new API token."""
data = await request.json()
name = data.get("name")
provider = data.get("provider", "openai")
if not name:
raise HTTPException(status_code=400, detail="Token name is required")
import secrets
from datetime import datetime, timezone
def _mut(auth_data):
tokens = auth_data.setdefault("tokens", [])
# max+1 (not len+1) so IDs aren't reused after a deletion.
token_id = max([t.get("id", 0) for t in tokens], default=0) + 1
new_token = {
"id": token_id,
"name": name,
"token": f"sk-coderai-{secrets.token_hex(32)}",
"provider": provider,
"created_at": datetime.now(timezone.utc).isoformat(),
"last_used": None,
}
tokens.append(new_token)
return True, new_token
new_token = session_manager.update_auth_data(_mut)
return {
"token": new_token["token"],
"id": new_token["id"],
"name": new_token["name"],
"provider": new_token["provider"]
}
@router.delete("/admin/api/tokens/{token_id}", summary="Revoke an API token")
async def api_delete_token(token_id: int, username: str = Depends(require_admin)):
"""Delete an API token."""
def _mut(auth_data):
tokens = auth_data.get("tokens", [])
new_tokens = [t for t in tokens if t["id"] != token_id]
if len(new_tokens) == len(tokens):
return False, False # not found → don't write
auth_data["tokens"] = new_tokens
return True, True
if not session_manager.update_auth_data(_mut):
raise HTTPException(status_code=404, detail="Token not found")
return {"success": True}
# --- Models management endpoints --- # --- Models management endpoints ---
...@@ -3412,13 +3074,9 @@ async def settings_page(request: Request, username: str = Depends(require_admin) ...@@ -3412,13 +3074,9 @@ async def settings_page(request: Request, username: str = Depends(require_admin)
return _tmpl(request, "settings.html", {"username": username, "is_admin": True}) return _tmpl(request, "settings.html", {"username": username, "is_admin": True})
@router.get("/admin/api/settings", summary="Get server settings") # /admin/api/settings GET is served by the front from the same
async def api_get_settings(username: str = Depends(require_admin)): # build_settings_dict() below (FrontProxy holds the same Config). The POST
"""Return current config.json as JSON.""" # (save) still lives here so the engine persists + applies config changes.
if config_manager is None or config_manager.config is None:
raise HTTPException(status_code=503, detail="Config manager not initialized")
return build_settings_dict(config_manager.config, _detect_gpu_cards())
def build_settings_dict(c, gpu_cards): def build_settings_dict(c, gpu_cards):
"""Pure ``Config`` → settings dict. Shared by the engine handler and the front """Pure ``Config`` → settings dict. Shared by the engine handler and the front
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment