mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-11 03:31:55 +00:00
feat(dashboard): support serving under URL prefix via X-Forwarded-Prefix
The Hermes dashboard previously assumed it was served at the root of its host (e.g. https://kanban.tilos.com/). When mounted behind a path-prefix reverse proxy (e.g. https://mission-control.tilos.com/hermes/), the SPA 404'd because: - index.html shipped absolute /assets/index-*.js URLs - React Router had no basename - The plugin loader hit /dashboard-plugins/<name>/... at the root host - CSS in the bundle had absolute url(/fonts/...) references This patch makes the dashboard prefix-aware at runtime, no rebuild required. The proxy injects 'X-Forwarded-Prefix: /hermes' on every request and the Python server: - Rewrites href/src in served index.html to '${prefix}/assets/...' - Injects 'window.__HERMES_BASE_PATH__="${prefix}"' for the SPA to read - Rewrites url() refs in CSS at serve time The SPA reads window.__HERMES_BASE_PATH__ once at boot and: - Prefixes all /api/... fetches via api.ts - Prefixes all /dashboard-plugins/... script/css URLs in usePlugins - Sets <BrowserRouter basename={...}> so client-side routing works When no X-Forwarded-Prefix header is present, behavior is unchanged (empty prefix => serves at root, kanban.tilos.com keeps working). Refs: MC-AUTO-13
This commit is contained in:
parent
6769060ae2
commit
52e2777821
4 changed files with 98 additions and 11 deletions
|
|
@ -1,4 +1,21 @@
|
|||
const BASE = "";
|
||||
// The dashboard can be served either at the root of its host (e.g.
|
||||
// https://kanban.tilos.com/) or under a URL prefix when reverse-proxied
|
||||
// (e.g. https://mission-control.tilos.com/hermes/). The Python backend
|
||||
// injects ``window.__HERMES_BASE_PATH__`` into index.html based on the
|
||||
// incoming ``X-Forwarded-Prefix`` header so the SPA can address its own
|
||||
// ``/api/...`` and ``/dashboard-plugins/...`` URLs correctly without a
|
||||
// rebuild. Empty string means "served at root".
|
||||
function readBasePath(): string {
|
||||
if (typeof window === "undefined") return "";
|
||||
const raw = window.__HERMES_BASE_PATH__ ?? "";
|
||||
if (!raw) return "";
|
||||
// Normalise: ensure leading slash, strip trailing slash.
|
||||
const withLead = raw.startsWith("/") ? raw : `/${raw}`;
|
||||
return withLead.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
export const HERMES_BASE_PATH = readBasePath();
|
||||
const BASE = HERMES_BASE_PATH;
|
||||
|
||||
import type { DashboardTheme } from "@/themes/types";
|
||||
|
||||
|
|
@ -7,6 +24,7 @@ import type { DashboardTheme } from "@/themes/types";
|
|||
declare global {
|
||||
interface Window {
|
||||
__HERMES_SESSION_TOKEN__?: string;
|
||||
__HERMES_BASE_PATH__?: string;
|
||||
}
|
||||
}
|
||||
let _sessionToken: string | null = null;
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ import { SystemActionsProvider } from "./contexts/SystemActions";
|
|||
import { I18nProvider } from "./i18n";
|
||||
import { exposePluginSDK } from "./plugins";
|
||||
import { ThemeProvider } from "./themes";
|
||||
import { HERMES_BASE_PATH } from "./lib/api";
|
||||
|
||||
// Expose the plugin SDK before rendering so plugins loaded via <script>
|
||||
// can access React, components, etc. immediately.
|
||||
exposePluginSDK();
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<BrowserRouter>
|
||||
<BrowserRouter basename={HERMES_BASE_PATH || undefined}>
|
||||
<I18nProvider>
|
||||
<ThemeProvider>
|
||||
<SystemActionsProvider>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { api } from "@/lib/api";
|
||||
import { api, HERMES_BASE_PATH } from "@/lib/api";
|
||||
import type { PluginManifest, RegisteredPlugin } from "./types";
|
||||
import {
|
||||
getPluginComponent,
|
||||
|
|
@ -43,7 +43,7 @@ export function usePlugins() {
|
|||
for (const manifest of manifests) {
|
||||
// Inject CSS if specified.
|
||||
if (manifest.css) {
|
||||
const cssUrl = `/dashboard-plugins/${manifest.name}/${manifest.css}`;
|
||||
const cssUrl = `${HERMES_BASE_PATH}/dashboard-plugins/${manifest.name}/${manifest.css}`;
|
||||
if (!document.querySelector(`link[href="${cssUrl}"]`)) {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
|
|
@ -55,7 +55,7 @@ export function usePlugins() {
|
|||
// Load JS bundle. In dev, cache-bust so Vite HMR can clear the
|
||||
// in-memory registry while the browser would otherwise never
|
||||
// re-execute a previously cached <script> URL.
|
||||
const baseUrl = `/dashboard-plugins/${manifest.name}/${manifest.entry}`;
|
||||
const baseUrl = `${HERMES_BASE_PATH}/dashboard-plugins/${manifest.name}/${manifest.entry}`;
|
||||
const scriptSrc = import.meta.env.DEV
|
||||
? `${baseUrl}?hermes_dv=${Date.now()}`
|
||||
: baseUrl;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue