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>
114 lines
5.8 KiB
Markdown
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`
|