From 0cdf261d40f0a3a427a8eeb01e704bc61fc8213b Mon Sep 17 00:00:00 2001 From: asepharyana Date: Thu, 20 Aug 2026 10:12:30 +0700 Subject: [PATCH] feat(phase8): observability dashboard at /dashboard Zero-dependency HTML page (served by the API, exposed on the domain): - live /metrics pull (queue gauges + uptime, 5s refresh, live-dot status) - search box calling MCP hybrid_search directly from the browser (CORS-open /mcp), ranked hits linking to the web doc route /docs/ - XSS-hardened: all KB fields esc()'d before innerHTML Verified live: /dashboard 200, /metrics 200, MCP hybrid_search returns real hits, doc links 200, typecheck green. --- PHASES.md | 23 +++++++++++++ apps/api/src/index.ts | 78 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/PHASES.md b/PHASES.md index e5eb6e8..2e91178 100644 --- a/PHASES.md +++ b/PHASES.md @@ -178,6 +178,29 @@ bun run api # Hono+tRPC API on :4020 (added /hooks/* webhooks) - Worker drained the MCP-enqueued job (completed count incremented, failed:0). - `turbo run typecheck` green across all 4 apps. +## Phase 8 — Dashboard (observability UI) ✅ DONE + +> The metrics endpoint existed (Phase 7) but had no consumer. Added a zero-dependency +> dashboard so the KB is actually observable + searchable from a browser. + +- [x] **`GET /dashboard`** on the API — self-contained HTML (no build, no deps) that: + - pulls `/metrics` (same origin) and renders queue gauges (waiting/active/completed/ + failed/delayed) + uptime, refreshing every 5s with a live-dot status indicator; + - runs a live **search box** that calls the MCP `hybrid_search` tool directly from the + browser (MCP `/mcp` is CORS-open), returning ranked hits that link to the web doc + page (`/docs/...`). +- [x] XSS hardening: all KB-sourced fields (`slug`/`title`/`section`/error message) + are `esc()`-escaped before `innerHTML` (defense-in-depth; data is server-trusted). +- [x] Caddy: `wiki.asepharyana.my.id/dashboard` → :4020. + +### Verification done (real) +- `https://wiki.asepharyana.my.id/dashboard` → 200, serves the page (title + JS present). +- `/metrics` → 200, 7 gauge lines including `mcpedia_queue_jobs{state=...}`. +- MCP `hybrid_search` from browser path returns real ranked hits (verified the exact + `tools/call` payload the dashboard issues; shape `{doc:{slug,title,section},rank}`). +- Dashboard link points to working web doc route `/docs/` (verified 200). +- `turbo run typecheck` green. + ## Decisions locked (from initial planning) - **Tooling:** bun workspaces + Turborepo (repo already used bun; pnpm rejected to minimize churn). diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index eef5864..32331c5 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -94,7 +94,83 @@ app.post("/hooks/index", async (c) => { return c.json({ ok: true, jobId: job.id, kind: "doc", relPath }); }); -// Mount tRPC at /trpc/*. The fetch adapter is the canonical Bun/Hono adapter. +// --- Phase 7: observability dashboard (public) --- +// Self-contained HTML page that reads /metrics (same origin) and queries the MCP +// server (/mcp, CORS-open) directly from the browser. No build step, no deps. +app.get("/dashboard", (c) => + c.html(` + + +MCPedia — Dashboard + + +

MCPedia Dashboard

+
+

Index Queue (BullMQ)

+

Service

+ +
+`), +); app.all("/trpc/*", (c) => fetchRequestHandler({ endpoint: "/trpc",