feat(web): hydrate dashboard tiles independently

This commit is contained in:
vince 2026-06-20 12:21:36 +02:00
parent 021f2cf20e
commit 23c8f0964c
7 changed files with 642 additions and 20 deletions

View file

@ -18,6 +18,9 @@ export interface DashboardRuntimeReady {
document: DashboardDocument;
schemaVersion: string;
currentRevisionId: string;
liveDatasourceHydration?: {
enabled: boolean;
};
}
export interface DashboardRuntimeEmpty {

View file

@ -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<DashboardTileResolution> {
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<StatusItem> {
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}`;
}