import * as fs from "node:fs"; import * as path from "node:path"; // The dashboard is a prebuilt React SPA under ui/web/dist. We serve its // index.html at "/" and its hashed assets from "/assets/*". If the build is // missing (developer hasn't run `just dashboard-build`), we fall back to a short // instructional page rather than a blank screen. export const DASHBOARD_DIR = path.resolve(import.meta.dir, "..", "..", "ui", "web", "dist"); const MIME: Record = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".mjs": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".png": "image/png", ".woff": "font/woff", ".woff2": "font/woff2", }; export function dashboardFile(relPath: string): Response | null { // Resolve safely inside DASHBOARD_DIR (no path traversal). const target = path.resolve(DASHBOARD_DIR, "." + (relPath.startsWith("/") ? relPath : "/" + relPath)); if (!target.startsWith(DASHBOARD_DIR)) return null; if (!fs.existsSync(target) || !fs.statSync(target).isFile()) return null; const body = fs.readFileSync(target); const ext = path.extname(target).toLowerCase(); const cacheable = relPath.startsWith("/assets/"); return new Response(body, { headers: { "content-type": MIME[ext] || "application/octet-stream", "cache-control": cacheable ? "public, max-age=31536000, immutable" : "no-cache", }, }); } export function dashboardHtml() { const index = dashboardFile("/index.html"); if (index) return index; return new Response( `

pi-hive dashboard not built

The dashboard bundle is missing at ${DASHBOARD_DIR}.

Build it once from the pi-hive repository root:

just dashboard-build

Then reload this page.

`, { status: 503, headers: { "content-type": "text/html; charset=utf-8" } }, ); }