admin: every remote/RunPod setting is now a form field

None of this session's work had any UI, so all of it was JSON-only. Now:

MODEL PAGE — a Placement section: serve this model from a remote URL
(with its name and token on that remote), and burst-to-RunPod with the
three triggers (no free slot / queue full / no local GPU) and a target
that is either a managed pod or your own serverless endpoint. The pod
form gained engine (auto/vLLM/llama.cpp/coderai/custom), the GGUF to
download, image, health path, docker args, the private-registry
credential, the pod's bearer token with an explicit "leave it open"
opt-out, and the scaling knobs (scale-up threshold, max in-flight per
pod, sticky sessions).

SETTINGS PAGE — a remote service URL for each of the five engines
(ds4, colibri, k3, ktransformers, vLLM), the account-wide registry
credential, and a Remote capabilities card: one row per capability,
each Local / Remote URL / RunPod pod, with image, shared pool name,
VRAM, $/hr, max pods and idle timeout.

Clearing a remote field now REMOVES it. The save path only writes keys
that are present, so sending an empty value as "unchanged" would have
left a stale remote in place and the model would have kept going to a
machine the user thought they had detached.

Tests assert what is easy to get silently wrong in UI work: every
getElementById reads an id that exists (counting ids the page assigns to
elements it creates), every new field is both populated and collected,
every capability the gateway can route has a row, and the admin API
keeps every key the form sends.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
parent 9ad9e19f
...@@ -2710,13 +2710,24 @@ async def api_model_configure(request: Request, username: str = Depends(require_ ...@@ -2710,13 +2710,24 @@ async def api_model_configure(request: Request, username: str = Depends(require_
"auto_compact_model", "suppress_reasoning", "auto_compact_model", "suppress_reasoning",
# Speech-to-text (wav2vec2 / vosk / NeMo-Canary) options. # Speech-to-text (wav2vec2 / vosk / NeMo-Canary) options.
"languages", "supports_translation", "keep_resident", "languages", "supports_translation", "keep_resident",
# Serve this model from somewhere else instead of loading it here: ):
# any OpenAI-compatible endpoint (another coderai, llama-server,
# vLLM, a rented pod). See codai/backends/remote_openai.py.
"service_url", "served_model", "api_key"):
if key in data: if key in data:
entry[key] = data[key] entry[key] = data[key]
# Serve this model from somewhere else instead of loading it here: any
# OpenAI-compatible endpoint (another coderai, llama-server, vLLM, a rented
# pod). See codai/backends/remote_openai.py. Blank REMOVES the setting — a
# key that is merely absent would leave a stale remote in place, and the
# model would keep going to a machine the user thought they had detached.
for key in ("service_url", "served_model", "api_key"):
if key in data:
val = (data.get(key) or "").strip() if isinstance(data.get(key), str) \
else data.get(key)
if val:
entry[key] = val
else:
entry.pop(key, None)
# Per-model ds4 launch overrides (only meaningful for a deepseek4 model served # Per-model ds4 launch overrides (only meaningful for a deepseek4 model served
# via the ds4 engine). Normalize to a small dict and drop it when empty, so the # via the ds4 engine). Normalize to a small dict and drop it when empty, so the
# entry stays clean and inherits the global ds4 config as the default. # entry stays clean and inherits the global ds4 config as the default.
...@@ -2773,7 +2784,10 @@ async def api_model_configure(request: Request, username: str = Depends(require_ ...@@ -2773,7 +2784,10 @@ async def api_model_configure(request: Request, username: str = Depends(require_
rpo = {} rpo = {}
# strings # strings
for k in ("mode", "selection_criteria", "gpu_type", "image", "served_model", for k in ("mode", "selection_criteria", "gpu_type", "image", "served_model",
"endpoint_id", "cost_period"): "endpoint_id", "cost_period",
# engine selection, private registries, and the pod's own token
"engine", "hf_gguf", "health_path", "docker_args",
"registry_auth_id", "api_key"):
v = src.get(k) v = src.get(k)
if isinstance(v, str) and v.strip(): if isinstance(v, str) and v.strip():
rpo[k] = v.strip() rpo[k] = v.strip()
...@@ -2788,14 +2802,15 @@ async def api_model_configure(request: Request, username: str = Depends(require_ ...@@ -2788,14 +2802,15 @@ async def api_model_configure(request: Request, username: str = Depends(require_
if ct: if ct:
rpo["cloud_types"] = ct rpo["cloud_types"] = ct
# bools # bools
for k in ("allow_spot",): for k in ("allow_spot", "allow_open_pod", "sticky_sessions"):
if k in src and src.get(k) is not None and src.get(k) != "": if k in src and src.get(k) is not None and src.get(k) != "":
sv = src.get(k) sv = src.get(k)
rpo[k] = (sv.lower() in ("1", "true", "on", "yes") rpo[k] = (sv.lower() in ("1", "true", "on", "yes")
if isinstance(sv, str) else bool(sv)) if isinstance(sv, str) else bool(sv))
# ints # ints
for k in ("container_disk_gb", "volume_gb", "port", "ctx", "min_pods", for k in ("container_disk_gb", "volume_gb", "port", "ctx", "min_pods",
"max_pods", "scale_up_inflight_per_pod", "idle_timeout_s", "max_pods", "scale_up_inflight_per_pod", "max_inflight_per_pod",
"idle_timeout_s",
"boot_timeout_s", "load_timeout_s", "min_workers", "max_workers"): "boot_timeout_s", "load_timeout_s", "min_workers", "max_workers"):
v = src.get(k) v = src.get(k)
if v not in (None, ""): if v not in (None, ""):
......
This diff is collapsed.
This diff is collapsed.
"""The admin UI must actually reach every setting — no JSON editing.
Two failure modes this catches, both invisible until someone tries to use the
page: a getElementById() for an id that no longer exists (silently reads/writes
nothing), and a form field the save path never collects (edits vanish on save).
"""
import re
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
TPL = ROOT / "codai" / "admin" / "templates"
def _ids_defined(html: str) -> set:
"""Ids in the markup, plus ones the page assigns to elements it creates."""
static = set(re.findall(r'\bid="([A-Za-z0-9_\-]+)"', html))
dynamic = set(re.findall(r"\.id\s*=\s*'([A-Za-z0-9_\-]+)'", html))
return static | dynamic
def _ids_referenced(html: str) -> set:
"""Only literal getElementById('x') calls — dynamic ids are built per row."""
return set(re.findall(r"getElementById\(\s*'([A-Za-z0-9_\-]+)'\s*\)", html))
@pytest.mark.parametrize("page", ["settings.html", "models.html"])
def test_every_referenced_element_exists(page):
html = (TPL / page).read_text()
missing = sorted(_ids_referenced(html) - _ids_defined(html))
assert missing == [], f"{page} reads ids that no element defines: {missing}"
def test_model_page_exposes_every_placement_setting():
html = (TPL / "models.html").read_text()
ids = _ids_defined(html)
for want in (
# serve this model from elsewhere
"cfg-remote-url", "cfg-remote-served", "cfg-remote-key",
# burst to RunPod
"cfg-spill-enabled", "cfg-spill-busy", "cfg-spill-queue",
"cfg-spill-nogpu", "cfg-spill-mode", "cfg-spill-endpoint",
"cfg-spill-engine", "cfg-spill-hf-gguf", "cfg-spill-image",
# pod shape, auth and scaling
"cfg-rp-engine", "cfg-rp-hf-gguf", "cfg-rp-api-key", "cfg-rp-open",
"cfg-rp-registry-auth", "cfg-rp-health-path", "cfg-rp-docker-args",
"cfg-rp-scale-up", "cfg-rp-max-inflight", "cfg-rp-sticky"):
assert want in ids, f"models.html has no field for {want}"
def test_model_page_saves_what_it_shows():
"""Every new field must be read back by the collect functions."""
html = (TPL / "models.html").read_text()
for want in ("cfg-remote-url", "cfg-spill-enabled", "cfg-spill-busy",
"cfg-rp-engine", "cfg-rp-api-key", "cfg-rp-open",
"cfg-rp-scale-up", "cfg-rp-max-inflight", "cfg-rp-sticky",
"cfg-rp-registry-auth", "cfg-rp-hf-gguf"):
assert html.count(f"'{want}'") >= 2, \
f"{want} is shown but never collected (or never populated)"
def test_settings_page_exposes_engine_service_urls_and_remotes():
html = (TPL / "settings.html").read_text()
ids = _ids_defined(html)
for engine in ("ds4", "colibri", "k3", "kt", "vllm"):
assert f"s-{engine}-service-url" in ids, f"no service URL field for {engine}"
assert html.count(f"'s-{engine}-service-url'") >= 2, \
f"{engine} service URL is shown but not loaded+saved"
for want in ("s-runpod-registry-auth", "s-remotes-enabled", "s-remotes-key",
"s-remotes-max-body", "s-remotes-rows"):
assert want in ids, f"settings.html has no field for {want}"
assert "_collectRemotes()" in html and "_populateRemotes(" in html
def test_every_gateway_capability_has_a_row_in_the_ui():
"""A capability the gateway can route but the UI cannot set would be
configurable only by editing JSON — exactly what this must avoid."""
from codai.api.remote_gateway import _CAPABILITY_PREFIXES
html = (TPL / "settings.html").read_text()
listed = set(re.findall(r"\['([a-z_]+)','[^']+','[^']*'\]", html))
routable = {cap for _, cap in _CAPABILITY_PREFIXES}
missing = sorted(routable - listed)
assert missing == [], f"capabilities with no UI row: {missing}"
def test_admin_accepts_every_pod_field_the_ui_sends():
"""The form can only be as good as the keys the backend keeps."""
routes = (ROOT / "codai" / "admin" / "routes.py").read_text()
for key in ("engine", "hf_gguf", "health_path", "docker_args",
"registry_auth_id", "api_key", "allow_open_pod",
"sticky_sessions", "max_inflight_per_pod", "on_busy",
"service_url", "served_model"):
assert f'"{key}"' in routes, f"admin API drops the {key} field"
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