feat(host-agent): serve built console via SPA-aware static mount

Add an optional single-process mode where the backend serves the built
console bundle itself, so operators don't need a separate `npm run dev`
for edge/dev setups. When RUNTIME_CONSOLE_STATIC_DIR points at the
console dist directory, the app mounts a SpaStaticFiles handler at /ui/
(with 404 fallback to index.html for client-side routing) and redirects
/ to /ui/. The console build uses an empty VITE_API_BASE_URL for relative
API paths (same-origin, no CORS), and Vite's base is set to /ui/ so
assets resolve under the mount. /console/* JSON API is unchanged and is
shared by both serve modes.

api.ts now treats an explicitly-empty VITE_API_BASE_URL as "use relative
paths" instead of falling back to the dev default, which previously
forced absolute URLs even in same-origin builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 13:29:58 +08:00
co-authored by Claude Opus 4.6
parent 8381b3068a
commit c0a653fa3f
4 changed files with 60 additions and 4 deletions
+16
View File
@@ -28,3 +28,19 @@ for local frontend development.
```bash
npm run build
```
## Same-Origin, Single-Process Mode
For an edge/dev setup where running a separate `npm run dev` process is too heavy,
the backend can serve the built console directly from the same process:
```bash
VITE_API_BASE_URL= npm run build
RUNTIME_CONSOLE_STATIC_DIR=$(pwd)/dist uvicorn api.rest:create_app --factory --host 127.0.0.1 --port 8000
```
`VITE_API_BASE_URL=` (empty) makes the build use relative API paths so it works
same-origin without CORS. The console is then served at `/ui/` (with `/`
redirecting there); `/console/*` remains the JSON API used by both this mode
and local `npm run dev`. Rebuild (`npm run build`) after frontend changes —
this mode does not hot-reload.
+3 -4
View File
@@ -7,10 +7,9 @@ import type {
} from "./types";
const configuredBaseUrl = import.meta.env.VITE_API_BASE_URL as string | undefined;
export const API_BASE_URL = (configuredBaseUrl || "http://127.0.0.1:8000").replace(
/\/$/,
"",
);
export const API_BASE_URL = (
configuredBaseUrl !== undefined ? configuredBaseUrl : "http://127.0.0.1:8000"
).replace(/\/$/, "");
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
+1
View File
@@ -3,4 +3,5 @@ import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
base: "/ui/",
});