diff --git a/apps/web/src/App.test.tsx b/apps/web/src/App.test.tsx
index fbb133d..71353f4 100644
--- a/apps/web/src/App.test.tsx
+++ b/apps/web/src/App.test.tsx
@@ -1,5 +1,6 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
+import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import { AppStateView } from "./App";
describe("React app dashboard state view", () => {
@@ -41,4 +42,35 @@ describe("React app dashboard state view", () => {
expect(html).toContain("Dark");
expect(html).toContain("Light");
});
+
+ test("renders the dashboard shell while individual items hydrate", () => {
+ const html = renderToString(
+ ,
+ );
+
+ expect(html).toContain("Operations Console");
+ expect(html).toContain("Service Uptime");
+ expect(html).toContain("Identity");
+ expect(html).toContain("Environment");
+ expect(html).not.toContain("Loading Dashboard");
+ expect(html).toContain(
+ 'data-severity="loading" data-model-id="service-uptime"',
+ );
+ expect(html).toContain('data-severity="loading" data-model-id="identity"');
+ expect(html).toContain('data-severity="loading" data-model-id="ambient"');
+ expect(html).toContain('data-severity="loading" data-model-id="runtime:status"');
+ });
});
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 0099173..6a19bd5 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,4 +1,11 @@
import { useEffect, useState } from "react";
+import type {
+ DashboardDocument,
+ DashboardModule,
+ ServiceEntry,
+ StatusItem,
+ TelemetryCard,
+} from "@dimensionlab/dashboard-model";
import type { DashboardRuntimeState } from "$lib/server/dashboard";
import { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer";
import {
@@ -9,21 +16,35 @@ import {
resolveInitialUiTheme,
type UiTheme,
type UiSeverity,
+ type UiDashboardPreview,
} from "@dimensionlab/ui";
-const loadingDashboardState: DashboardRuntimeState = {
- state: "loading",
- title: "Loading Dashboard",
- subtitle: "Fetching active model",
- message: "Waiting for the active dashboard document.",
-};
+type DashboardTileReference =
+ | { kind: "telemetry"; id: string }
+ | { kind: "service"; id: string }
+ | { kind: "module"; id: string }
+ | { kind: "status"; stripId: string; id: string };
+
+type DashboardTileResponse =
+ | {
+ state: "ready";
+ tile: DashboardTileReference;
+ item: DashboardModule | ServiceEntry | StatusItem | TelemetryCard;
+ }
+ | {
+ state: "not_found";
+ tile: DashboardTileReference;
+ message: string;
+ };
export function AppStateView({
dashboard,
onThemeChange,
theme,
+ hydratingItemIds,
}: {
dashboard: DashboardRuntimeState;
+ hydratingItemIds?: ReadonlySet;
onThemeChange?: (theme: UiTheme) => void;
theme?: UiTheme;
}) {
@@ -33,9 +54,14 @@ export function AppStateView({
) : null;
if (dashboard.state === "ready") {
+ const uiDashboard = markHydratingItems(
+ dashboardDocumentToUiDashboard(dashboard.document),
+ hydratingItemIds,
+ );
+
return (
);
@@ -89,7 +115,10 @@ export function resolveDocumentMetadata(dashboard: DashboardRuntimeState): {
export default function App() {
const [dashboard, setDashboard] =
- useState(loadingDashboardState);
+ useState(null);
+ const [hydratingItemIds, setHydratingItemIds] = useState>(
+ () => new Set(),
+ );
const [theme, setTheme] = useState(() => {
if (typeof window === "undefined") return "dark";
@@ -97,6 +126,8 @@ export default function App() {
});
useEffect(() => {
+ if (!dashboard) return;
+
const metadata = resolveDocumentMetadata(dashboard);
document.title = metadata.title;
@@ -119,6 +150,7 @@ export default function App() {
useEffect(() => {
let cancelled = false;
let refreshTimer: number | undefined;
+ let hydrationRun = 0;
async function loadDashboard() {
const response = await fetch("/api/dashboard");
@@ -127,6 +159,18 @@ export default function App() {
if (cancelled) return;
setDashboard(nextDashboard);
+ const currentRun = ++hydrationRun;
+ if (
+ nextDashboard.state === "ready" &&
+ nextDashboard.liveDatasourceHydration?.enabled !== false
+ ) {
+ const tiles = dashboardHydrationTiles(nextDashboard.document);
+ setHydratingItemIds(new Set(tiles.map(dashboardTileKey)));
+ hydrateDashboardTiles(tiles, currentRun);
+ } else {
+ setHydratingItemIds(new Set());
+ }
+
if (refreshTimer) {
window.clearInterval(refreshTimer);
refreshTimer = undefined;
@@ -145,6 +189,54 @@ export default function App() {
}
}
+ function hydrateDashboardTiles(
+ tiles: DashboardTileReference[],
+ run: number,
+ ) {
+ tiles.forEach((tile) => {
+ void hydrateDashboardTile(tile, run);
+ });
+ }
+
+ async function hydrateDashboardTile(
+ tile: DashboardTileReference,
+ run: number,
+ ) {
+ const key = dashboardTileKey(tile);
+
+ try {
+ const response = await fetch(dashboardTileUrl(tile));
+ const tileResponse = (await response.json()) as DashboardTileResponse;
+
+ if (
+ cancelled ||
+ run !== hydrationRun ||
+ !response.ok ||
+ tileResponse.state !== "ready"
+ ) {
+ return;
+ }
+
+ const readyTileResponse = tileResponse;
+ setDashboard((current) =>
+ current?.state === "ready"
+ ? {
+ ...current,
+ document: applyDashboardTile(current.document, readyTileResponse),
+ }
+ : current,
+ );
+ } finally {
+ if (!cancelled && run === hydrationRun) {
+ setHydratingItemIds((current) => {
+ const next = new Set(current);
+ next.delete(key);
+ return next;
+ });
+ }
+ }
+ }
+
void loadDashboard();
return () => {
@@ -153,13 +245,14 @@ export default function App() {
};
}, []);
- return (
+ return dashboard ? (
- );
+ ) : null;
}
function stateSeverity(state: DashboardRuntimeState["state"]): UiSeverity {
@@ -181,3 +274,144 @@ function getThemeStorage(): Storage | undefined {
return undefined;
}
}
+
+function markHydratingItems(
+ dashboard: UiDashboardPreview,
+ hydratingItemIds?: ReadonlySet,
+): UiDashboardPreview {
+ if (!hydratingItemIds?.size) return dashboard;
+
+ return {
+ ...dashboard,
+ telemetry: dashboard.telemetry.map((card) =>
+ hydratingItemIds.has(`telemetry:${card.id}`)
+ ? {
+ ...card,
+ severity: "loading",
+ detail: "loading live telemetry",
+ }
+ : card,
+ ),
+ serviceGroups: dashboard.serviceGroups.map((group) => ({
+ ...group,
+ services: group.services.map((service) =>
+ hydratingItemIds.has(`service:${service.id}`)
+ ? {
+ ...service,
+ severity: "loading",
+ detail: "loading",
+ }
+ : service,
+ ),
+ })),
+ modules: dashboard.modules.map((module) =>
+ hydratingItemIds.has(`module:${module.id}`)
+ ? {
+ ...module,
+ severity: "loading",
+ detail: "loading live data",
+ }
+ : module,
+ ),
+ statusItems: dashboard.statusItems.map((item) =>
+ hydratingItemIds.has(`status:${item.id}`)
+ ? {
+ ...item,
+ severity: "loading",
+ value: "loading",
+ }
+ : item,
+ ),
+ };
+}
+
+function dashboardHydrationTiles(document: DashboardDocument): DashboardTileReference[] {
+ const telemetry = document.telemetry
+ .filter((card) => card.datasource?.type === "external")
+ .map((card): DashboardTileReference => ({ kind: "telemetry", id: card.id }));
+ const services = document.serviceGroups.flatMap((group) =>
+ group.services
+ .filter((service) => service.datasource?.type === "external")
+ .map((service): DashboardTileReference => ({ kind: "service", id: service.id })),
+ );
+ const modules = (document.modules || [])
+ .filter((module) =>
+ module.datasource?.type === "external" ||
+ module.id === "runtime-health-summary"
+ )
+ .map((module): DashboardTileReference => ({ kind: "module", id: module.id }));
+ const status = document.statusStrips.flatMap((strip) =>
+ strip.items
+ .filter((item) => item.id !== "auto-refresh")
+ .map((item): DashboardTileReference => ({
+ kind: "status",
+ stripId: strip.id,
+ id: item.id,
+ })),
+ );
+
+ return [...telemetry, ...services, ...modules, ...status];
+}
+
+function dashboardTileKey(tile: DashboardTileReference): string {
+ return tile.kind === "status"
+ ? `${tile.kind}:${tile.stripId}:${tile.id}`
+ : `${tile.kind}:${tile.id}`;
+}
+
+function dashboardTileUrl(tile: DashboardTileReference): string {
+ const parts = tile.kind === "status"
+ ? ["api", "dashboard", "tile", tile.kind, tile.stripId, tile.id]
+ : ["api", "dashboard", "tile", tile.kind, tile.id];
+ return `/${parts.map(encodeURIComponent).join("/")}`;
+}
+
+function applyDashboardTile(
+ document: DashboardDocument,
+ response: Extract,
+): DashboardDocument {
+ if (response.tile.kind === "telemetry") {
+ return {
+ ...document,
+ telemetry: document.telemetry.map((card) =>
+ card.id === response.tile.id ? response.item as TelemetryCard : card,
+ ),
+ };
+ }
+
+ if (response.tile.kind === "service") {
+ return {
+ ...document,
+ serviceGroups: document.serviceGroups.map((group) => ({
+ ...group,
+ services: group.services.map((service) =>
+ service.id === response.tile.id ? response.item as ServiceEntry : service,
+ ),
+ })),
+ };
+ }
+
+ if (response.tile.kind === "module") {
+ return {
+ ...document,
+ modules: (document.modules || []).map((module) =>
+ module.id === response.tile.id ? response.item as DashboardModule : module,
+ ),
+ };
+ }
+
+ const tile = response.tile;
+ return {
+ ...document,
+ statusStrips: document.statusStrips.map((strip) =>
+ strip.id === tile.stripId
+ ? {
+ ...strip,
+ items: strip.items.map((item) =>
+ item.id === tile.id ? response.item as StatusItem : item,
+ ),
+ }
+ : strip,
+ ),
+ };
+}
diff --git a/apps/web/src/lib/server/dashboard.ts b/apps/web/src/lib/server/dashboard.ts
index 47ebfa2..33afa3a 100644
--- a/apps/web/src/lib/server/dashboard.ts
+++ b/apps/web/src/lib/server/dashboard.ts
@@ -18,6 +18,9 @@ export interface DashboardRuntimeReady {
document: DashboardDocument;
schemaVersion: string;
currentRevisionId: string;
+ liveDatasourceHydration?: {
+ enabled: boolean;
+ };
}
export interface DashboardRuntimeEmpty {
diff --git a/apps/web/src/lib/server/datasources/index.ts b/apps/web/src/lib/server/datasources/index.ts
index d69db86..ff0c675 100644
--- a/apps/web/src/lib/server/datasources/index.ts
+++ b/apps/web/src/lib/server/datasources/index.ts
@@ -10,6 +10,30 @@ import type {
TelemetryCard,
} from "@dimensionlab/dashboard-model";
+export type DashboardTileReference =
+ | { kind: "telemetry"; id: string }
+ | { kind: "service"; id: string }
+ | { kind: "module"; id: string }
+ | { kind: "status"; stripId: string; id: string };
+
+export type DashboardTileItem =
+ | DashboardModule
+ | ServiceEntry
+ | StatusItem
+ | TelemetryCard;
+
+export type DashboardTileResolution =
+ | {
+ state: "ready";
+ tile: DashboardTileReference;
+ item: DashboardTileItem;
+ }
+ | {
+ state: "not_found";
+ tile: DashboardTileReference;
+ message: string;
+ };
+
export interface DatasourceResolutionOptions {
fetch?: DatasourceFetch;
prometheusBaseUrl?: string;
@@ -46,6 +70,69 @@ export async function resolveDashboardDatasources(
};
}
+export async function resolveDashboardTile(
+ document: DashboardDocument,
+ tile: DashboardTileReference,
+ options: DatasourceResolutionOptions = {},
+): Promise {
+ const context = datasourceContext(options);
+
+ if (tile.kind === "telemetry") {
+ const card = document.telemetry.find((item) => item.id === tile.id);
+ if (!card) return missingTile(tile);
+
+ return {
+ state: "ready",
+ tile,
+ item: await resolveTelemetryCard(card, context),
+ };
+ }
+
+ if (tile.kind === "service") {
+ const service = document.serviceGroups
+ .flatMap((group) => group.services)
+ .find((item) => item.id === tile.id);
+ if (!service) return missingTile(tile);
+
+ return {
+ state: "ready",
+ tile,
+ item: await resolveService(service, context),
+ };
+ }
+
+ if (tile.kind === "module") {
+ const module = document.modules?.find((item) => item.id === tile.id);
+ if (!module) return missingTile(tile);
+
+ const item = module.id === "runtime-health-summary"
+ ? runtimeHealthSummary(
+ module,
+ await Promise.all(
+ document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
+ ),
+ )
+ : await resolveModule(module, context);
+
+ return { state: "ready", tile, item };
+ }
+
+ const strip = document.statusStrips.find((item) => item.id === tile.stripId);
+ const statusItem = strip?.items.find((item) => item.id === tile.id);
+ if (!strip || !statusItem) return missingTile(tile);
+
+ return {
+ state: "ready",
+ tile,
+ item: await resolveStatusTile(
+ statusItem,
+ document.metadata.refreshIntervalSeconds,
+ document.serviceGroups,
+ context,
+ ),
+ };
+}
+
interface DatasourceContext {
fetch: DatasourceFetch;
prometheusBaseUrl: string;
@@ -390,6 +477,68 @@ function resolveStatusItem(
return structuredClone(item);
}
+async function resolveStatusTile(
+ item: StatusItem,
+ refreshIntervalSeconds: number | undefined,
+ serviceGroups: ServiceGroup[],
+ context: DatasourceContext,
+): Promise {
+ if (item.id === "system-status") {
+ const resolvedGroups = await Promise.all(
+ serviceGroups.map((group) => resolveServiceGroup(group, context)),
+ );
+ const health = serviceHealthSummary(resolvedGroups);
+ return {
+ ...structuredClone(item),
+ value: health.value,
+ severity: health.severity,
+ };
+ }
+
+ if (item.id === "last-sync") {
+ return {
+ ...structuredClone(item),
+ value: "just now",
+ severity: "ok",
+ };
+ }
+
+ if (item.id === "uptime") {
+ const uptime = await prometheusScalar(
+ 'time() - node_boot_time_seconds{job="node",host="linux-infra"}',
+ context,
+ ).catch(() => null);
+ return uptime === null
+ ? structuredClone(item)
+ : {
+ ...structuredClone(item),
+ value: formatDuration(uptime),
+ severity: "ok",
+ };
+ }
+
+ if (item.id === "load-avg") {
+ const loadAverage = await prometheusLoadAverage(context).catch(() => null);
+ return loadAverage
+ ? {
+ ...structuredClone(item),
+ value: loadAverage,
+ severity: "neutral",
+ }
+ : structuredClone(item);
+ }
+
+ if (item.id === "auto-refresh" && refreshIntervalSeconds) {
+ return {
+ ...structuredClone(item),
+ value: `${refreshIntervalSeconds}s`,
+ severity: "neutral",
+ };
+ }
+
+ return structuredClone(item);
+}
+
function serviceHealthSummary(serviceGroups: ServiceGroup[]): {
severity: Severity;
value: string;
@@ -673,3 +822,17 @@ function formatDuration(totalSeconds: number): string {
const minutes = Math.floor((seconds % 3_600) / 60);
return `${days}d ${hours}h ${minutes}m`;
}
+
+function missingTile(tile: DashboardTileReference): DashboardTileResolution {
+ return {
+ state: "not_found",
+ tile,
+ message: `Dashboard tile not found: ${tileKey(tile)}`,
+ };
+}
+
+function tileKey(tile: DashboardTileReference): string {
+ return tile.kind === "status"
+ ? `${tile.kind}:${tile.stripId}:${tile.id}`
+ : `${tile.kind}:${tile.id}`;
+}
diff --git a/apps/web/src/server/index.ts b/apps/web/src/server/index.ts
index 860d042..ba2135f 100644
--- a/apps/web/src/server/index.ts
+++ b/apps/web/src/server/index.ts
@@ -1,6 +1,6 @@
import { extname, normalize } from "node:path";
import { handleAgentDashboardRoute } from "./routes/agent-dashboard";
-import { handleDashboardRoute } from "./routes/dashboard";
+import { handleDashboardRoute, handleDashboardTileRoute } from "./routes/dashboard";
const host = process.env.HOST || "0.0.0.0";
const port = Number(process.env.PORT || 3000);
@@ -23,6 +23,11 @@ export async function handleRequest(request: Request): Promise {
return handleDashboardRoute();
}
+ if (url.pathname.startsWith("/api/dashboard/tile/")) {
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
+ return handleDashboardTileRoute(url.pathname);
+ }
+
if (url.pathname === "/api/agent/dashboard") {
if (request.method !== "POST") return methodNotAllowed(["POST"]);
return handleAgentDashboardRoute(request);
diff --git a/apps/web/src/server/routes/dashboard.test.ts b/apps/web/src/server/routes/dashboard.test.ts
index 21ad58d..ae68e78 100644
--- a/apps/web/src/server/routes/dashboard.test.ts
+++ b/apps/web/src/server/routes/dashboard.test.ts
@@ -1,11 +1,14 @@
-import { describe, expect, test } from "vitest";
+import { describe, expect, test, vi } from "vitest";
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
-import { loadDashboardResponse } from "./dashboard";
+import { loadDashboardResponse, loadDashboardTileResponse } from "./dashboard";
describe("dashboard API route", () => {
- test("returns ready dashboard runtime state from the existing model loader", async () => {
+ test("returns the ready dashboard shell without hydrating live datasources", async () => {
+ const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue(
+ new Error("live datasource fetch should not run for the shell response"),
+ );
+
const response = await loadDashboardResponse({
- disableLiveDatasources: true,
refreshSeedDocument: true,
seedIfEmpty: true,
});
@@ -15,5 +18,105 @@ describe("dashboard API route", () => {
expect(response.document.metadata.title).toBe(
dimensionLabDashboardFixture.metadata.title,
);
+ expect(fetch).not.toHaveBeenCalled();
+
+ fetch.mockRestore();
+ });
+
+ test("reports when client-side live hydration is disabled", async () => {
+ const previous = process.env.DISABLE_LIVE_DATASOURCES;
+ process.env.DISABLE_LIVE_DATASOURCES = "1";
+
+ try {
+ const response = await loadDashboardResponse({
+ refreshSeedDocument: true,
+ seedIfEmpty: true,
+ });
+
+ expect(response.state).toBe("ready");
+ if (response.state !== "ready") throw new Error("expected ready dashboard");
+ expect(response.liveDatasourceHydration).toEqual({ enabled: false });
+ } finally {
+ if (previous === undefined) {
+ delete process.env.DISABLE_LIVE_DATASOURCES;
+ } else {
+ process.env.DISABLE_LIVE_DATASOURCES = previous;
+ }
+ }
+ });
+
+ test("hydrates a telemetry tile independently from the dashboard shell", async () => {
+ const fetch = vi.fn(async (input: RequestInfo | URL) => {
+ const url = String(input);
+
+ if (url.startsWith("https://prometheus.example/api/v1/query_range")) {
+ return jsonResponse({
+ status: "success",
+ data: {
+ result: [
+ {
+ metric: { host: "linux-infra" },
+ values: [
+ [1771430000, "10"],
+ [1771430060, "20"],
+ [1771430120, "42"],
+ ],
+ },
+ ],
+ },
+ });
+ }
+
+ if (url.startsWith("https://prometheus.example/api/v1/query")) {
+ return jsonResponse({
+ status: "success",
+ data: {
+ result: [
+ {
+ metric: { host: "linux-infra" },
+ value: [1771430400, "42"],
+ },
+ ],
+ },
+ });
+ }
+
+ throw new Error(`Unhandled test request: ${url}`);
+ });
+
+ const response = await loadDashboardTileResponse(
+ { kind: "telemetry", id: "infra-ram" },
+ {
+ fetch,
+ prometheusBaseUrl: "https://prometheus.example",
+ refreshSeedDocument: true,
+ seedIfEmpty: true,
+ },
+ );
+
+ expect(response.state).toBe("ready");
+ if (response.state !== "ready") throw new Error("expected ready tile");
+ expect(response.tile).toEqual({ kind: "telemetry", id: "infra-ram" });
+ expect(response.item).toMatchObject({
+ id: "infra-ram",
+ value: { kind: "percent", value: 42 },
+ severity: "ok",
+ detail: "linux-infra",
+ sparkline: [10, 20, 42],
+ });
+ expect(fetch).toHaveBeenCalledWith(
+ expect.stringContaining("/api/v1/query?"),
+ expect.objectContaining({ cache: "no-store" }),
+ );
+ expect(fetch).toHaveBeenCalledWith(
+ expect.stringContaining("/api/v1/query_range?"),
+ expect.objectContaining({ cache: "no-store" }),
+ );
});
});
+
+function jsonResponse(payload: unknown): Response {
+ return new Response(JSON.stringify(payload), {
+ headers: { "content-type": "application/json" },
+ });
+}
diff --git a/apps/web/src/server/routes/dashboard.ts b/apps/web/src/server/routes/dashboard.ts
index 21c30e4..d4a6b72 100644
--- a/apps/web/src/server/routes/dashboard.ts
+++ b/apps/web/src/server/routes/dashboard.ts
@@ -3,19 +3,30 @@ import {
type DashboardRuntimeOptions,
type DashboardRuntimeState,
} from "$lib/server/dashboard";
-import { resolveDashboardDatasources } from "$lib/server/datasources";
+import {
+ resolveDashboardDatasources,
+ resolveDashboardTile,
+ type DashboardTileReference,
+ type DashboardTileResolution,
+ type DatasourceResolutionOptions,
+} from "$lib/server/datasources";
export interface LoadDashboardResponseOptions
extends Pick<
DashboardRuntimeOptions,
"refreshSeedDocument" | "seedIfEmpty" | "seedDocument"
- > {
+ >,
+ DatasourceResolutionOptions {
disableLiveDatasources?: boolean;
+ hydrateLiveDatasources?: boolean;
}
export async function loadDashboardResponse(
options: LoadDashboardResponseOptions = {},
): Promise {
+ const liveHydrationEnabled =
+ !options.disableLiveDatasources &&
+ process.env.DISABLE_LIVE_DATASOURCES !== "1";
const dashboard = loadDashboardRuntime(undefined, {
refreshSeedDocument: options.refreshSeedDocument ?? true,
seedIfEmpty: options.seedIfEmpty ?? true,
@@ -26,16 +37,87 @@ export async function loadDashboardResponse(
return dashboard;
}
- if (options.disableLiveDatasources || process.env.DISABLE_LIVE_DATASOURCES === "1") {
- return dashboard;
+ if (
+ !options.hydrateLiveDatasources ||
+ options.disableLiveDatasources ||
+ process.env.DISABLE_LIVE_DATASOURCES === "1"
+ ) {
+ return {
+ ...dashboard,
+ liveDatasourceHydration: {
+ enabled: liveHydrationEnabled,
+ },
+ };
}
return {
...dashboard,
- document: await resolveDashboardDatasources(dashboard.document),
+ document: await resolveDashboardDatasources(dashboard.document, options),
+ liveDatasourceHydration: {
+ enabled: false,
+ },
};
}
+export async function loadDashboardTileResponse(
+ tile: DashboardTileReference,
+ options: LoadDashboardResponseOptions = {},
+): Promise {
+ const dashboard = loadDashboardRuntime(undefined, {
+ refreshSeedDocument: options.refreshSeedDocument ?? true,
+ seedIfEmpty: options.seedIfEmpty ?? true,
+ seedDocument: options.seedDocument,
+ });
+
+ if (dashboard.state !== "ready") {
+ return {
+ state: "not_found",
+ tile,
+ message: `Dashboard is not ready: ${dashboard.state}`,
+ };
+ }
+
+ return resolveDashboardTile(dashboard.document, tile, options);
+}
+
export async function handleDashboardRoute(): Promise {
return Response.json(await loadDashboardResponse());
}
+
+export async function handleDashboardTileRoute(pathname: string): Promise {
+ const tile = parseDashboardTilePath(pathname);
+ if (!tile) {
+ return Response.json(
+ { ok: false, message: "Invalid dashboard tile route" },
+ { status: 404 },
+ );
+ }
+
+ const response = await loadDashboardTileResponse(tile);
+ return Response.json(response, {
+ status: response.state === "ready" ? 200 : 404,
+ });
+}
+
+function parseDashboardTilePath(pathname: string): DashboardTileReference | null {
+ const parts = pathname.split("/").filter(Boolean);
+ const [, dashboard, tileRoot, kind, firstId, secondId] = parts;
+ if (dashboard !== "dashboard" || tileRoot !== "tile" || !kind || !firstId) {
+ return null;
+ }
+
+ const id = decodeURIComponent(firstId);
+ if (kind === "telemetry" || kind === "service" || kind === "module") {
+ return { kind, id };
+ }
+
+ if (kind === "status" && secondId) {
+ return {
+ kind,
+ stripId: id,
+ id: decodeURIComponent(secondId),
+ };
+ }
+
+ return null;
+}