Implements the cloud-console OpenSpec change: adds GET /v1/tasks (filterable,
bounded pagination, tasks:read) and GET /v1/tasks/{id}/attempts (404 on unknown
task) to the platform SDK, with matching CloudClient methods and a closed-by-
default CLOUD_CONSOLE_CORS_ORIGINS allow-list wired through CloudControlConfig.
Ships an independent Vue 3 + Vite SPA at cloud-console/ that authenticates with
an operator-supplied bearer token held in sessionStorage, renders tasks with
attempt history, device pool, host registry, and the plugin registry with a
registration form.
Backend test suite: 438 passed (-m "not integration"); cloud-console typecheck
and production build both succeed. PostgreSQL-backed repository tests and
manual end-to-end verification remain pending external infrastructure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cloud Console
Independent Vue 3 + Vite single-page app for the Cloud Control Plane
(apps/cloud-api). Operators authenticate by pasting a pre-issued scoped
bearer token; the console stores it in sessionStorage, attaches
Authorization: Bearer <token> to every request, and clears it whenever the
Cloud API responds 401 or 403.
The app talks only to the platform SDK surface (/v1/...) and consumes the
two listing endpoints added by the cloud-console change (GET /v1/tasks,
GET /v1/tasks/{task_id}/attempts) alongside the existing
/v1/devices, /v1/hosts, /v1/plugins, and POST /v1/plugins routes.
Prerequisites
- Node.js 20+ (matching the existing
console/SPA project) - A running Cloud API (
apps/cloud-api) reachable from your browser - A bearer token issued via
CLOUD_PUBLIC_CREDENTIALS_JSONwhose scopes cover what you intend to do from the console. Recommended least-privilege set:tasks:read— task list and attempt history viewspool:read— device and host viewsplugins:read— plugin list- Add
tasks:submit/plugins:adminonly if you need the write actions from the same tab.
Configure the backend CORS allow-list
The Cloud API has no CORS middleware by default. Before a browser can call it
cross-origin, set CLOUD_CONSOLE_CORS_ORIGINS to a comma-separated allow-list
that includes the exact origin your dev server prints (scheme + host + port —
no trailing slash):
# Example: allow the default Vite dev origin
export CLOUD_CONSOLE_CORS_ORIGINS="http://127.0.0.1:5173"
Restart apps/cloud-api after changing this env. Tokens are still required —
the allow-list only says which browser origins may send them.
Run the dev server
cd cloud-console
cp .env.example .env.local
# Edit .env.local if your Cloud API is not at http://127.0.0.1:8001
npm install
npm run dev
Vite prints a local URL (default http://127.0.0.1:5173). Open it, paste a
bearer token, and the task/device/host/plugin dashboards become available.
.env.local overrides the default base URL via VITE_CLOUD_API_BASE_URL
(defaults to http://127.0.0.1:8001).
Build for production
npm run build # type-checks with vue-tsc, then emits dist/
npm run preview # serves the built bundle locally
dist/ is a static bundle — host it behind any static file server or CDN and
point it at a deployed Cloud API via VITE_CLOUD_API_BASE_URL set at build
time.
Token handling
- The token is held in
sessionStorageonly. Closing the tab discards it. - Every API request attaches
Authorization: Bearer <token>and targets only the configuredVITE_CLOUD_API_BASE_URL. - A
401/403response clears the stored token and returns the operator to the token-entry screen with the API's error detail.
Project layout
cloud-console/
├── src/
│ ├── api.ts # API client wrapper (token storage, fetch, errors)
│ ├── types.ts # TS interfaces mirroring the REST models
│ ├── App.vue # Shell: token gate, nav, view router
│ ├── main.ts # Vue bootstrap
│ ├── style.css # Dark theme styles
│ └── views/
│ ├── TokenScreen.vue
│ ├── TasksView.vue # list + detail with attempt history
│ ├── DevicesView.vue # device pool + host registry
│ └── PluginsView.vue # registry list + registration form
├── index.html
├── package.json
├── tsconfig.json / tsconfig.node.json
└── vite.config.ts