feat(api): server-rendered Jinja2 Runtime console at /ui/
Replaces the separate Vue/Vite `console/` SPA with a same-origin, server-rendered console built on a module-level Jinja2 Environment with select_autoescape(["html","xml"]). - Add api/console_web.py with /ui/ routes (dashboard, tasks, task detail/timeline, config) and a _status_fragment polled every 10s. - Refactor api/console.py into a typed ConsoleService shared by the JSON and HTML routers so validation/persistence cannot drift. - Remove RUNTIME_CONSOLE_STATIC_DIR, SpaStaticFiles, and the wildcard CORS middleware from api/rest.py; GET / now redirects to /ui/. - Delete the top-level console/ project; add jinja2 and python-multipart as direct dependencies and ship templates/CSS/JS via package-data. - Add 31 tests (XSS probes, PRG flows, fragment refresh, no-static-dir and no-CORS regressions, wheel-packaging smoke test). /console/* JSON endpoints remain unchanged. The console keeps the trusted-network-only boundary; auth/CSRF is intentionally deferred. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Smoke test that console templates and assets ship inside the Runtime wheel.
|
||||
|
||||
Builds ``device-agent-runtime`` into a temporary directory, installs it into an
|
||||
isolated venv that cannot reach the source checkout, and asserts the packaged
|
||||
``api`` package carries the Jinja2 templates and static assets needed by
|
||||
``/ui/``. This guards against setuptools package-data regressions that would
|
||||
let the console work from an editable checkout but break from a real install.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import venv
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _run(cmd: list[str], *, cwd: Path | None = None) -> str:
|
||||
return subprocess.check_output(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_runtime_wheel_packages_console_templates_and_assets(tmp_path: Path) -> None:
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
|
||||
wheel_dir = tmp_path / "wheels"
|
||||
wheel_dir.mkdir()
|
||||
_run(
|
||||
["uv", "build", "--package", "device-agent-runtime", "--wheel", "--no-sources"],
|
||||
cwd=repo_root,
|
||||
)
|
||||
wheels = list(repo_root.glob("dist/*.whl"))
|
||||
assert wheels, "uv build did not produce a wheel"
|
||||
wheel_path = wheels[0]
|
||||
|
||||
venv_dir = tmp_path / "venv"
|
||||
venv.create(venv_dir, with_pip=True, clear=True)
|
||||
pip = str(venv_dir / "Scripts" / "pip.exe")
|
||||
if not Path(pip).exists():
|
||||
pip = str(venv_dir / "bin" / "pip")
|
||||
_run([pip, "install", str(wheel_path)], cwd=tmp_path)
|
||||
|
||||
python = str(venv_dir / "Scripts" / "python.exe")
|
||||
if not Path(python).exists():
|
||||
python = str(venv_dir / "bin" / "python")
|
||||
|
||||
probe = _run(
|
||||
[
|
||||
python,
|
||||
"-c",
|
||||
(
|
||||
"from importlib.resources import files; "
|
||||
"api_root = files('api'); "
|
||||
"templates = sorted(p.name for p in "
|
||||
"(api_root / 'templates' / 'runtime_console').iterdir()); "
|
||||
"assets = sorted(p.name for p in "
|
||||
"(api_root / 'static' / 'runtime_console').iterdir()); "
|
||||
"print(','.join(templates)); "
|
||||
"print(','.join(assets))"
|
||||
),
|
||||
],
|
||||
cwd=tmp_path,
|
||||
)
|
||||
template_names, asset_names = probe.strip().splitlines()
|
||||
assert "base.html" in template_names
|
||||
assert "dashboard.html" in template_names
|
||||
assert "config.html" in template_names
|
||||
assert "console.css" in asset_names
|
||||
assert "dashboard.js" in asset_names
|
||||
|
||||
# Clean up the build artifact so it does not leak into the working tree.
|
||||
for wheel in wheels:
|
||||
wheel.unlink()
|
||||
Reference in New Issue
Block a user