fix(control-ui): serve dashboard at root
This commit is contained in:
@@ -248,7 +248,7 @@ struct MenuContent: View {
|
|||||||
default:
|
default:
|
||||||
components.scheme = "http"
|
components.scheme = "http"
|
||||||
}
|
}
|
||||||
components.path = "/ui/"
|
components.path = "/"
|
||||||
components.query = nil
|
components.query = nil
|
||||||
guard let url = components.url else {
|
guard let url = components.url else {
|
||||||
throw NSError(domain: "Dashboard", code: 2, userInfo: [
|
throw NSError(domain: "Dashboard", code: 2, userInfo: [
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ read_when:
|
|||||||
|
|
||||||
The Control UI is a small **Vite + Lit** single-page app served by the Gateway under:
|
The Control UI is a small **Vite + Lit** single-page app served by the Gateway under:
|
||||||
|
|
||||||
- `http://<host>:18789/ui/`
|
- `http://<host>:18789/` (preferred)
|
||||||
|
- `http://<host>:18789/ui/` (legacy alias)
|
||||||
|
|
||||||
It speaks **directly to the Gateway WebSocket** on the same port.
|
It speaks **directly to the Gateway WebSocket** on the same port.
|
||||||
|
|
||||||
@@ -48,4 +49,3 @@ pnpm ui:dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then point the UI at your Gateway WS URL (e.g. `ws://127.0.0.1:18789`).
|
Then point the UI at your Gateway WS URL (e.g. `ws://127.0.0.1:18789`).
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export type CanvasHostConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type GatewayControlUiConfig = {
|
export type GatewayControlUiConfig = {
|
||||||
/** If false, the Gateway will not serve the Control UI under /ui/. Default: true. */
|
/** If false, the Gateway will not serve the Control UI (/, /ui/). Default: true. */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
|||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const UI_PREFIX = "/ui/";
|
const UI_PREFIX = "/ui/";
|
||||||
|
const ROOT_PREFIX = "/";
|
||||||
|
|
||||||
function resolveControlUiRoot(): string | null {
|
function resolveControlUiRoot(): string | null {
|
||||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
@@ -88,13 +89,11 @@ export function handleControlUiHttpRequest(
|
|||||||
|
|
||||||
if (url.pathname === "/ui") {
|
if (url.pathname === "/ui") {
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.setHeader("Location", UI_PREFIX);
|
res.setHeader("Location", "/");
|
||||||
res.end();
|
res.end();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url.pathname.startsWith(UI_PREFIX)) return false;
|
|
||||||
|
|
||||||
const root = resolveControlUiRoot();
|
const root = resolveControlUiRoot();
|
||||||
if (!root) {
|
if (!root) {
|
||||||
res.statusCode = 503;
|
res.statusCode = 503;
|
||||||
@@ -105,7 +104,12 @@ export function handleControlUiHttpRequest(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rel = url.pathname.slice(UI_PREFIX.length);
|
const rel = (() => {
|
||||||
|
if (url.pathname === ROOT_PREFIX) return "";
|
||||||
|
if (url.pathname.startsWith(UI_PREFIX)) return url.pathname.slice(UI_PREFIX.length);
|
||||||
|
if (url.pathname.startsWith("/assets/")) return url.pathname.slice(1);
|
||||||
|
return url.pathname.slice(1);
|
||||||
|
})();
|
||||||
const requested = rel && !rel.endsWith("/") ? rel : `${rel}index.html`;
|
const requested = rel && !rel.endsWith("/") ? rel : `${rel}index.html`;
|
||||||
const fileRel = requested || "index.html";
|
const fileRel = requested || "index.html";
|
||||||
if (!isSafeRelativePath(fileRel)) {
|
if (!isSafeRelativePath(fileRel)) {
|
||||||
|
|||||||
@@ -848,12 +848,6 @@ export async function startGatewayServer(
|
|||||||
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
||||||
|
|
||||||
if (controlUiEnabled) {
|
if (controlUiEnabled) {
|
||||||
if (req.url === "/") {
|
|
||||||
res.statusCode = 302;
|
|
||||||
res.setHeader("Location", "/ui/");
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (handleControlUiHttpRequest(req, res)) return;
|
if (handleControlUiHttpRequest(req, res)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": ["vite/client"],
|
"types": ["vite/client"],
|
||||||
"useDefineForClassFields": false
|
"useDefineForClassFields": false
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { defineConfig } from "vite";
|
|||||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: "/ui/",
|
base: "/",
|
||||||
build: {
|
build: {
|
||||||
outDir: path.resolve(here, "../dist/control-ui"),
|
outDir: path.resolve(here, "../dist/control-ui"),
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user