Files
q792602257andClaude Opus 4.6 e00c50e703 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>
2026-07-15 08:03:13 +08:00

114 lines
5.8 KiB
Markdown

## ADDED Requirements
### Requirement: Runtime console pages SHALL be served by the Runtime FastAPI application
The Runtime FastAPI application SHALL serve its operator console from
same-origin `/ui/` routes without requiring a separately running frontend
process, a prebuilt SPA directory, or `RUNTIME_CONSOLE_STATIC_DIR`. `GET /`
SHALL redirect an operator to `/ui/`.
#### Scenario: Open the built-in console without static-directory configuration
- **WHEN** the Runtime application starts without `RUNTIME_CONSOLE_STATIC_DIR`
- **THEN** `GET /` redirects to `/ui/` and `GET /ui/` returns an HTML
dashboard rendered by the Runtime process
#### Scenario: Navigate the operator workflows through page routes
- **WHEN** an operator opens `/ui/tasks`, `/ui/tasks/{task_id}`, or
`/ui/config`
- **THEN** the Runtime returns HTML pages for task browsing, task timeline
detail, and device/runtime configuration respectively
### Requirement: Runtime console HTML SHALL use one autoescaping template environment
Every Runtime console HTML response SHALL be rendered through one
process-wide Jinja2 environment configured to autoescape `.html` and `.xml`
templates. Page handlers SHALL NOT construct HTML through f-strings, string
concatenation, or post-process rendered output to bypass that environment.
#### Scenario: Untrusted device and task values are rendered safely
- **WHEN** a device name, task goal, failure reason, or timeline value contains
`<script>alert(1)</script>`
- **THEN** the rendered console HTML contains an escaped text representation
and contains no script element originating from that value
#### Scenario: A future page inherits HTML autoescaping
- **WHEN** a future Runtime console route renders a `.html` template through
the shared environment
- **THEN** its interpolated values are HTML-escaped without route-specific
escaping configuration
### Requirement: Runtime console pages SHALL preserve console operational workflows
The server-rendered console SHALL let an operator inspect device status, list
and filter tasks, inspect a task's ordered timeline including available
screenshots, register or remove a supported device, and view or update
`max_steps`. Successful configuration mutations SHALL use POST/Redirect/GET;
invalid form input SHALL be re-rendered as a readable HTML error without
applying a partial mutation.
#### Scenario: Browse filtered tasks and inspect a timeline
- **WHEN** an operator selects a device or status filter and opens a known
task
- **THEN** the task list contains only matching tasks and the task page renders
its timeline in step order with its screenshot when one exists
#### Scenario: Register a device from the configuration page
- **WHEN** an operator submits valid supported-device form values
- **THEN** the Runtime registers and persists the device, responds with a
redirect to the configuration page, and the device is visible after the
redirect
#### Scenario: Reject invalid configuration without partial mutation
- **WHEN** an operator submits an unsupported driver type, malformed
connection value, or non-positive `max_steps`
- **THEN** the Runtime returns an HTML validation error, preserves the prior
Runtime/configuration state, and does not perform a redirect
### Requirement: Dashboard live status SHALL remain server-rendered
The dashboard SHALL retain periodic live-status refresh without restoring a
client-side application framework. Its browser enhancement SHALL request a
server-rendered HTML fragment and replace only the live-status region; it
SHALL NOT rebuild console state from a JSON API response.
#### Scenario: Refresh dashboard status after the polling interval
- **WHEN** the dashboard refresh enhancement runs while the Runtime is
available
- **THEN** it retrieves a Jinja2-rendered status fragment and updates the
dashboard's live-status region without a full-page reload
### Requirement: Existing console JSON API SHALL remain compatible
The Runtime SHALL continue to expose the existing `/console/devices`,
`/console/tasks`, `/console/tasks/{task_id}`, `/console/tasks/{task_id}/timeline`,
and `/console/config` JSON endpoints with their existing methods, status
codes, payloads, filtering behavior, and persistence semantics. Page and JSON
routes SHALL use the same API-local console operations rather than issuing
HTTP requests to each other.
#### Scenario: Programmatic client reads console data after the UI migration
- **WHEN** a client calls an existing `GET /console/*` endpoint after the
server-rendered console is deployed
- **THEN** it receives the same JSON response shape and status behavior as
before the migration
#### Scenario: A page mutation is visible through the JSON API
- **WHEN** an operator registers or removes a device or updates `max_steps`
through a `/ui/` form
- **THEN** the corresponding `/console/*` JSON endpoint reports the same
resulting Runtime state
### Requirement: Runtime console templates and assets SHALL ship with the Python package
The Runtime distribution SHALL package all console templates and static assets
needed by `/ui/`. The Runtime console SHALL not depend on the top-level Vue/Vite
`console/` project, Node package installation, Vite configuration, or the
SPA-only static-directory mount. Same-origin console operation SHALL not
require wildcard CORS configured for Vite development.
#### Scenario: Run the console from an installed Runtime wheel
- **WHEN** `device-agent-runtime` is built and installed outside the source
checkout
- **THEN** the Runtime can serve `/ui/` and its required CSS/browser assets
from packaged resources
#### Scenario: Start a Runtime after the SPA workflow is removed
- **WHEN** an operator starts the Runtime API using the documented Python
command
- **THEN** the console is available at `/ui/` without `npm install`,
`npm run build`, `VITE_API_BASE_URL`, or `RUNTIME_CONSOLE_STATIC_DIR`