Skip to content

Commit de47c40

Browse files
authored
feat(dashboard): add instance health dashboard on GET / endpoint (#286)
* feat(dashboard): add Instance Health Dashboard on GET / endpoint - Add dashboard module with metrics collector, HTML template, and handler - Dashboard shows server info, registered instances, health status, sessions - Supports content negotiation: HTML for browsers, JSON for API clients - Add DASHBOARD_ENABLED config (default true, disable with =false) - Instances show health status (healthy/degraded/offline) based on metrics - HTML includes auto-refresh every 30 seconds - Safe for MCP clients - they use POST /mcp, not GET / - Add 44 unit tests for metrics, handler, and HTML template Closes #275 * fix(docs): rename gtag.d.ts to gtag.ts for Vite compatibility (#287) - Vite/Rollup cannot resolve .d.ts files as runtime imports - Renamed declaration file to regular .ts (content unchanged) - File contains `declare global` + `export {}` which is valid in both formats Fixes #285 * fix(dashboard): add error handling and safe URL parsing - Add try/catch in dashboardHandler for collectMetrics/renderDashboard failures - Add safe URL parsing in renderInstanceCard with fallback to raw URL - Use GITLAB_TOKEN config constant instead of direct process.env access * fix(dashboard): remove unused parameter and improve URL parsing - Remove unused lastSuccessfulRequestMs parameter from determineInstanceStatus - Add .js extension to dashboard import in server.ts - Add safe URL parsing for session hostnames in html-template.ts - Add comprehensive tests for collectMetrics function * test(dashboard): achieve 100% line coverage - Add tests for error handling in dashboardHandler - Add tests for getMetrics export function - Add tests for OAuth and token auth modes - Add tests for unknown status indicator - Add tests for invalid URL parsing fallbacks - Add tests for queue filling warning - Add tests for formatRelativeTime (hours, days) - Add tests for invalid URL in sessions byInstance Coverage: Lines 100%, Branches 92.85%
1 parent 25b2c1a commit de47c40

File tree

9 files changed

+2187
-0
lines changed

9 files changed

+2187
-0
lines changed

src/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ export const RATE_LIMIT_SESSION_MAX_REQUESTS = parseInt(
241241
// TLS/SSL configuration
242242
export const SKIP_TLS_VERIFY = process.env.SKIP_TLS_VERIFY === "true";
243243

244+
// Dashboard configuration
245+
// When enabled, GET / returns health dashboard (HTML or JSON based on Accept header)
246+
// When disabled, GET / is handled by MCP StreamableHTTP transport
247+
export const DASHBOARD_ENABLED = process.env.DASHBOARD_ENABLED !== "false";
248+
244249
// Proxy configuration
245250
export const HTTP_PROXY = process.env.HTTP_PROXY;
246251
export const HTTPS_PROXY = process.env.HTTPS_PROXY;

src/dashboard/handler.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Dashboard HTTP Handler
3+
*
4+
* Handles GET / requests and returns:
5+
* - HTML dashboard when Accept header includes text/html or wildcard
6+
* - JSON metrics when Accept header is application/json
7+
*/
8+
9+
import { Request, Response } from "express";
10+
import { collectMetrics, DashboardMetrics } from "./metrics.js";
11+
import { renderDashboard } from "./html-template.js";
12+
import { logDebug, logError } from "../logger.js";
13+
14+
// Determine if request prefers HTML response
15+
//
16+
// Returns true for:
17+
// - Accept: text/html
18+
// - Accept: */* (browser default)
19+
// - No Accept header (treat as browser)
20+
//
21+
// Returns false for:
22+
// - Accept: application/json
23+
function prefersHtml(req: Request): boolean {
24+
const accept = req.headers.accept ?? "*/*";
25+
26+
// Explicit JSON request
27+
if (accept.includes("application/json") && !accept.includes("text/html")) {
28+
return false;
29+
}
30+
31+
// HTML or wildcard (browser default)
32+
return accept.includes("text/html") || accept.includes("*/*");
33+
}
34+
35+
/**
36+
* Dashboard endpoint handler
37+
*
38+
* Returns server health dashboard as HTML or JSON based on Accept header.
39+
* Safe for MCP clients - they use POST /mcp or SSE endpoints, not GET /.
40+
*
41+
* @param req - Express request
42+
* @param res - Express response
43+
*/
44+
export function dashboardHandler(req: Request, res: Response): void {
45+
try {
46+
const metrics = collectMetrics();
47+
48+
logDebug("Dashboard request", {
49+
accept: req.headers.accept,
50+
prefersHtml: prefersHtml(req),
51+
});
52+
53+
if (prefersHtml(req)) {
54+
res.type("text/html").send(renderDashboard(metrics));
55+
} else {
56+
res.json(metrics);
57+
}
58+
} catch (error) {
59+
logError("Dashboard error", { err: error });
60+
res.status(500).json({ error: "Failed to generate dashboard" });
61+
}
62+
}
63+
64+
/**
65+
* Get dashboard metrics without rendering
66+
* Useful for programmatic access or testing
67+
*/
68+
export function getMetrics(): DashboardMetrics {
69+
return collectMetrics();
70+
}

0 commit comments

Comments
 (0)