refactor(web): move website into turbo app workspace

This commit is contained in:
vince 2026-06-20 05:41:36 +02:00
parent 2664804e91
commit b4e626a868
66 changed files with 318 additions and 298 deletions

32
apps/web/Containerfile Normal file
View file

@ -0,0 +1,32 @@
FROM docker.io/oven/bun:1.3.14 AS deps
WORKDIR /repo
ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH
COPY package.json bun.lock turbo.json tsconfig.base.json ./
COPY apps/web/package.json apps/web/package.json
COPY packages/ui/package.json packages/ui/package.json
RUN bun install --frozen-lockfile
FROM deps AS build
COPY . .
RUN bun install --frozen-lockfile && bun run build
FROM deps AS runtime
WORKDIR /repo/apps/web
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
ENV DATABASE_URL=file:/data/dimensionlab.sqlite
ENV DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle
COPY --from=build /repo/apps/web/build ./build
COPY --from=build /repo/apps/web/dist ./dist
COPY --from=build /repo/apps/web/drizzle ./drizzle
RUN mkdir -p /data
VOLUME ["/data"]
EXPOSE 3000
CMD ["bun", "build/index.js"]

View file

@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/lib/server/db/schema.ts",
out: "./drizzle",
dialect: "sqlite",
dbCredentials: {
url: process.env.DATABASE_URL?.replace(/^file:/, "") || "./data/dimensionlab.sqlite",
},
});

View file

@ -0,0 +1,20 @@
CREATE TABLE `dashboard_documents` (
`id` text PRIMARY KEY NOT NULL,
`current_revision_id` text,
`created_at` integer NOT NULL,
`updated_at` integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE `dashboard_revisions` (
`id` text PRIMARY KEY NOT NULL,
`dashboard_id` text NOT NULL,
`schema_version` text NOT NULL,
`document` text NOT NULL,
`actor` text NOT NULL,
`message` text,
`operation` text NOT NULL,
`source_revision_id` text,
`created_at` integer NOT NULL
);
--> statement-breakpoint
CREATE INDEX `idx_dashboard_revisions_dashboard_created` ON `dashboard_revisions` (`dashboard_id`,`created_at`);

View file

@ -0,0 +1,138 @@
{
"version": "6",
"dialect": "sqlite",
"id": "00000000-0000-0000-0000-dashboard0000",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"dashboard_documents": {
"name": "dashboard_documents",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"current_revision_id": {
"name": "current_revision_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"dashboard_revisions": {
"name": "dashboard_revisions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"dashboard_id": {
"name": "dashboard_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"schema_version": {
"name": "schema_version",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"document": {
"name": "document",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"actor": {
"name": "actor",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"operation": {
"name": "operation",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"source_revision_id": {
"name": "source_revision_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_dashboard_revisions_dashboard_created": {
"name": "idx_dashboard_revisions_dashboard_created",
"columns": [
"dashboard_id",
"created_at"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"enums": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
},
"internal": {
"indexes": {}
}
}

View file

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1781805600000,
"tag": "0000_dashboard_persistence",
"breakpoints": true
}
]
}

22
apps/web/index.html Normal file
View file

@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Dimension Lab dashboard runtime" />
<title>Dimension Lab</title>
<script>
try {
const theme = localStorage.getItem("dashboard-ui-theme");
document.documentElement.dataset.uiTheme =
theme === "light" ? "light" : "dark";
} catch {
document.documentElement.dataset.uiTheme = "dark";
}
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
"dev": "bun src/server/dev.ts",
"build": "vite build && bun build src/server/index.ts --target bun --outdir build",
"preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js",
"check": "tsc --noEmit",
@ -22,7 +22,8 @@
"ajv-formats": "^3.0.1",
"drizzle-orm": "^0.45.2",
"react": "^19.2.7",
"react-dom": "^19.2.7"
"react-dom": "^19.2.7",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@axe-core/playwright": "^4.11.3",
@ -33,6 +34,7 @@
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"bun-types": "^1.3.14",
"drizzle-kit": "^0.31.10",
"msw": "^2.14.6",
"shadcn": "^4.11.0",

View file

@ -0,0 +1,51 @@
import { defineConfig, devices } from "@playwright/test";
const port = Number(process.env.PLAYWRIGHT_PORT || 4173);
const baseURL = `http://127.0.0.1:${port}`;
const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007);
const storybookURL = `http://127.0.0.1:${storybookPort}`;
const databaseUrl =
process.env.PLAYWRIGHT_DATABASE_URL ||
`file:./data/playwright-${process.pid}-${Date.now()}.sqlite`;
export default defineConfig({
testDir: "tests/e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? [["github"], ["list"]] : "list",
use: {
baseURL,
trace: "retain-on-failure",
screenshot: "only-on-failure",
},
webServer: [
{
command: `DISABLE_LIVE_DATASOURCES=1 bun run build && DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`,
url: baseURL,
reuseExistingServer: false,
timeout: 120_000,
},
{
command: `cd ../.. && bun run build-storybook && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`,
url: storybookURL,
reuseExistingServer: false,
timeout: 120_000,
},
],
projects: [
{
name: "chromium-desktop",
use: {
...devices["Desktop Chrome"],
viewport: { width: 1440, height: 1000 },
},
},
{
name: "chromium-mobile",
use: {
...devices["Pixel 7"],
},
},
],
});

44
apps/web/src/App.test.tsx Normal file
View file

@ -0,0 +1,44 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { AppStateView } from "./App";
describe("React app dashboard state view", () => {
test("renders loading dashboard state", () => {
const html = renderToString(
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
/>,
);
expect(html).toContain("Loading Dashboard");
expect(html).toContain("Fetching active model");
});
test("renders an accessible theme toggle with the active theme", () => {
const html = renderToString(
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
theme="dark"
onThemeChange={() => undefined}
/>,
);
expect(html).toContain('data-ui-theme-toggle="true"');
expect(html).toContain('data-ui-theme-current="dark"');
expect(html).toContain('aria-label="Light theme"');
expect(html).toContain('aria-pressed="false"');
expect(html).toContain("Theme");
expect(html).toContain("Dark");
expect(html).toContain("Light");
});
});

183
apps/web/src/App.tsx Normal file
View file

@ -0,0 +1,183 @@
import { useEffect, useState } from "react";
import type { DashboardRuntimeState } from "$lib/server/dashboard";
import { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer";
import {
DashboardFrame,
SystemState,
ThemeToggle,
persistUiTheme,
resolveInitialUiTheme,
type UiTheme,
type UiSeverity,
} from "@dimensionlab/ui";
const loadingDashboardState: DashboardRuntimeState = {
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
};
export function AppStateView({
dashboard,
onThemeChange,
theme,
}: {
dashboard: DashboardRuntimeState;
onThemeChange?: (theme: UiTheme) => void;
theme?: UiTheme;
}) {
const themeToggle =
theme && onThemeChange ? (
<ThemeToggle theme={theme} onThemeChange={onThemeChange} />
) : null;
if (dashboard.state === "ready") {
return (
<DashboardFrame
dashboard={dashboardDocumentToUiDashboard(dashboard.document)}
actions={themeToggle}
/>
);
}
const detail = `${dashboard.subtitle}: ${dashboard.message}`;
const errors = dashboard.state === "invalid" ? dashboard.errors : [];
return (
<main className="state-shell" data-dashboard-state={dashboard.state}>
{themeToggle ? (
<div className="state-shell__actions">{themeToggle}</div>
) : null}
<SystemState
title={dashboard.title}
detail={detail}
severity={stateSeverity(dashboard.state)}
icon={stateIcon(dashboard.state)}
/>
{errors.length ? (
<ul aria-label="Validation errors">
{errors.map((error) => (
<li key={error}>{error}</li>
))}
</ul>
) : null}
</main>
);
}
export function resolveDocumentMetadata(dashboard: DashboardRuntimeState): {
description: string;
title: string;
} {
if (dashboard.state === "ready") {
const uiDashboard = dashboardDocumentToUiDashboard(dashboard.document);
return {
title: uiDashboard.title,
description:
uiDashboard.subtitle ||
dashboard.document.metadata.description ||
uiDashboard.title,
};
}
return {
title: dashboard.title,
description: dashboard.subtitle || dashboard.message,
};
}
export default function App() {
const [dashboard, setDashboard] =
useState<DashboardRuntimeState>(loadingDashboardState);
const [theme, setTheme] = useState<UiTheme>(() => {
if (typeof window === "undefined") return "dark";
return resolveInitialUiTheme(getThemeStorage());
});
useEffect(() => {
const metadata = resolveDocumentMetadata(dashboard);
document.title = metadata.title;
let description = document.querySelector<HTMLMetaElement>(
'meta[name="description"]',
);
if (!description) {
description = document.createElement("meta");
description.name = "description";
document.head.append(description);
}
description.content = metadata.description;
}, [dashboard]);
useEffect(() => {
document.documentElement.dataset.uiTheme = theme;
persistUiTheme(theme, getThemeStorage());
}, [theme]);
useEffect(() => {
let cancelled = false;
let refreshTimer: number | undefined;
async function loadDashboard() {
const response = await fetch("/api/dashboard");
const nextDashboard = (await response.json()) as DashboardRuntimeState;
if (cancelled) return;
setDashboard(nextDashboard);
if (refreshTimer) {
window.clearInterval(refreshTimer);
refreshTimer = undefined;
}
const refreshIntervalSeconds =
nextDashboard.state === "ready"
? nextDashboard.document.metadata.refreshIntervalSeconds
: undefined;
if (refreshIntervalSeconds) {
refreshTimer = window.setInterval(
() => void loadDashboard(),
refreshIntervalSeconds * 1000,
);
}
}
void loadDashboard();
return () => {
cancelled = true;
if (refreshTimer) window.clearInterval(refreshTimer);
};
}, []);
return (
<AppStateView
dashboard={dashboard}
theme={theme}
onThemeChange={setTheme}
/>
);
}
function stateSeverity(state: DashboardRuntimeState["state"]): UiSeverity {
if (state === "invalid") return "danger";
if (state === "loading") return "loading";
return "stale";
}
function stateIcon(state: DashboardRuntimeState["state"]): string {
if (state === "invalid") return "mdi:file-alert-outline";
if (state === "loading") return "mdi:progress-clock";
return "mdi:tray";
}
function getThemeStorage(): Storage | undefined {
try {
return window.localStorage;
} catch {
return undefined;
}
}

170
apps/web/src/app.css Normal file
View file

@ -0,0 +1,170 @@
@import "tailwindcss";
@import "@dimensionlab/ui/styles.css";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--font-heading: var(--font-sans);
--font-sans: 'Geist Variable', sans-serif;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
}
:root,
.dark {
--background: var(--ui-color-canvas);
--foreground: var(--ui-color-text);
--card: var(--ui-color-surface);
--card-foreground: var(--ui-color-text);
--popover: var(--ui-color-surface-raised);
--popover-foreground: var(--ui-color-text);
--primary: var(--ui-color-accent);
--primary-foreground: var(--ui-color-canvas);
--secondary: var(--ui-color-surface-raised);
--secondary-foreground: var(--ui-color-text);
--muted: var(--ui-color-surface-raised);
--muted-foreground: var(--ui-color-muted);
--accent: var(--ui-color-accent);
--accent-foreground: var(--ui-color-canvas);
--destructive: var(--ui-color-danger);
--border: var(--ui-color-border);
--input: var(--ui-color-border-strong);
--ring: var(--ui-color-accent);
--chart-1: var(--ui-color-accent);
--chart-2: var(--ui-color-ok);
--chart-3: var(--ui-color-warning);
--chart-4: var(--ui-color-danger);
--chart-5: var(--ui-color-stale);
--radius: 0.5rem;
--sidebar: var(--ui-color-surface);
--sidebar-foreground: var(--ui-color-text);
--sidebar-primary: var(--ui-color-accent);
--sidebar-primary-foreground: var(--ui-color-canvas);
--sidebar-accent: var(--ui-color-surface-raised);
--sidebar-accent-foreground: var(--ui-color-text);
--sidebar-border: rgba(244, 244, 244, 0.16);
--sidebar-ring: var(--ui-color-accent);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
}
}
html {
background: var(--ui-color-canvas);
}
body {
min-width: 320px;
min-height: 100vh;
margin: 0;
background:
radial-gradient(circle at 50% 12%, transparent 0 42%, var(--ui-color-backdrop-vignette) 100%),
linear-gradient(var(--ui-color-grid-line) 1px, transparent 1px),
linear-gradient(90deg, var(--ui-color-grid-line) 1px, transparent 1px),
var(--ui-color-canvas);
background-size: auto, 40px 40px, 40px 40px, auto;
color: var(--ui-color-text);
font-family: var(--ui-font-mono);
text-rendering: geometricPrecision;
}
button,
input,
textarea,
select {
font: inherit;
}
button:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
outline: 0;
box-shadow: var(--ui-focus-ring);
}
a {
color: inherit;
}
.state-shell {
display: grid;
min-height: 100vh;
align-content: center;
gap: var(--ui-space-3);
padding: var(--ui-space-4);
}
.state-shell__actions {
justify-self: center;
}
.state-shell ul {
display: grid;
max-width: 56rem;
gap: var(--ui-space-2);
margin: 0;
border: var(--ui-border);
background: var(--ui-color-surface-module);
color: var(--ui-color-muted);
font-size: 0.76rem;
list-style-position: inside;
padding: var(--ui-space-3);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}
}

View file

@ -0,0 +1,148 @@
import { describe, expect, test } from "vitest";
import {
dimensionLabDashboardFixture,
} from "./dimensionlab";
import type {
DashboardDocument,
DatasourceReference,
ServiceEntry,
TelemetryCard,
} from "../schema";
describe("Dimension Lab dashboard seed", () => {
test("defines the primary first-screen sections as model data", () => {
expect(dimensionLabDashboardFixture.metadata.title).toBe("System Overview");
expect(dimensionLabDashboardFixture.layout.telemetry).toHaveLength(16);
expect(dimensionLabDashboardFixture.layout.serviceGroups).toEqual([
"essentials",
"monitoring",
"ai-automation",
"systems",
"runtime-health",
]);
expect(dimensionLabDashboardFixture.layout.statusStrips).toEqual([
"footer-status",
]);
expect(dimensionLabDashboardFixture.layout.modules).toEqual([
"weather-amsterdam",
"runtime-health-summary",
]);
});
test("keeps icon, link, and datasource references in seed data", () => {
const iconIds = [
...dimensionLabDashboardFixture.telemetry.map((card) => card.icon),
...allServices(dimensionLabDashboardFixture).map((service) => service.icon),
...(dimensionLabDashboardFixture.modules || []).map((module) => module.icon),
].filter((icon): icon is string => Boolean(icon));
expect(
dimensionLabDashboardFixture.telemetry.every(
(card) => card.icon && card.datasource,
),
).toBe(true);
expect(
dimensionLabDashboardFixture.telemetry.some(
(card) => card.datasource?.type === "external" &&
card.datasource.adapter === "prometheus",
),
).toBe(true);
expect(
allServices(dimensionLabDashboardFixture).some(
(service) => service.datasource?.type === "external" &&
service.datasource.adapter === "http-status",
),
).toBe(true);
for (const service of allServices(dimensionLabDashboardFixture)) {
expect(service.icon, `${service.id} must declare an Iconify id`).toBeTruthy();
expect(verifiedSeedIconIds.has(service.icon || ""), `${service.id} icon must be verified`).toBe(true);
expect(service.datasource, `${service.id} must declare health source`).toBeTruthy();
if (service.link) {
expect(service.link.label, `${service.id} link needs an accessible label`).toBeTruthy();
expect(service.link.external).toBe(true);
}
}
for (const icon of iconIds) {
expect(verifiedSeedIconIds.has(icon), `${icon} must be verified`).toBe(true);
}
});
test("labels fallback values and unresolved adapters explicitly", () => {
const placeholders = collectDatasources(dimensionLabDashboardFixture).filter(
(datasource) => datasource.type === "placeholder",
);
const fallbackText = [
...dimensionLabDashboardFixture.telemetry.map((card) => card.detail || ""),
...allServices(dimensionLabDashboardFixture).map((service) => service.detail || ""),
...dimensionLabDashboardFixture.statusStrips.flatMap((strip) =>
strip.items.map((item) => item.value),
),
...(dimensionLabDashboardFixture.modules || []).map((module) => module.detail || ""),
].join("\n").toLowerCase();
expect(fallbackText).toContain("fallback");
for (const service of allServices(dimensionLabDashboardFixture)) {
expect(service.detail?.toLowerCase(), `${service.id} detail must label fallback state`).toContain("fallback");
}
for (const statusItem of dimensionLabDashboardFixture.statusStrips.flatMap((strip) => strip.items)) {
expect(statusItem.value.toLowerCase(), `${statusItem.id} value must label fallback state`).toContain("fallback");
}
expect(placeholders.length).toBeGreaterThan(0);
for (const datasource of placeholders) {
expect(datasource.reason.toLowerCase()).toMatch(/fallback|pending|unresolved/);
}
});
});
function allServices(document: DashboardDocument): ServiceEntry[] {
return document.serviceGroups.flatMap((group) => group.services);
}
function collectDatasources(document: DashboardDocument): DatasourceReference[] {
const telemetry = document.telemetry
.map((card: TelemetryCard) => card.datasource)
.filter((datasource): datasource is DatasourceReference => Boolean(datasource));
const services = allServices(document)
.map((service) => service.datasource)
.filter((datasource): datasource is DatasourceReference => Boolean(datasource));
const modules = (document.modules || [])
.map((module) => module.datasource)
.filter((datasource): datasource is DatasourceReference => Boolean(datasource));
return [...telemetry, ...services, ...modules];
}
const verifiedSeedIconIds = new Set([
"mdi:account-hard-hat-outline",
"mdi:backup-restore",
"mdi:brain",
"mdi:cpu-64-bit",
"mdi:docker",
"mdi:expansion-card",
"mdi:fan",
"mdi:harddisk",
"mdi:image-edit-outline",
"mdi:memory",
"mdi:pulse",
"mdi:robot-outline",
"mdi:router-network",
"mdi:text-box-search",
"mdi:thermometer",
"mdi:web",
"mdi:weather-sunny",
"simple-icons:adguard",
"simple-icons:adminer",
"simple-icons:amazonwebservices",
"simple-icons:cockpit",
"simple-icons:forgejo",
"simple-icons:grafana",
"simple-icons:n8n",
"simple-icons:ollama",
"simple-icons:postgresql",
"simple-icons:prometheus",
"simple-icons:uptimekuma",
"simple-icons:vaultwarden",
"simple-icons:wikidotjs",
]);

View file

@ -0,0 +1,681 @@
import {
DASHBOARD_SCHEMA_VERSION,
type DashboardDocument,
type DatasourceReference,
type MetricValue,
type ServiceEntry,
type ServiceGroup,
type Severity,
type TelemetryCard,
} from "../schema";
type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number";
interface MetricSeed {
id: string;
label: string;
icon: string;
kind: NumericValueKind;
value: number;
detail: string;
severity: Severity;
datasource: DatasourceReference;
thresholds?: TelemetryCard["thresholds"];
sparkline?: number[];
precision?: number;
}
interface ServiceSeed {
id: string;
label: string;
description: string;
icon: string;
datasource: DatasourceReference;
href?: string;
severity?: Severity;
detail?: string;
}
const REAL_FILESYSTEM_FILTER =
'fstype!~"tmpfs|overlay|squashfs|nsfs|tracefs|autofs|proc|sysfs|cgroup2|devtmpfs|securityfs|debugfs|pstore|bpf|configfs|selinuxfs|mqueue|hugetlbfs|fusectl|ramfs"';
const USER_MOUNT_FILTER =
'mountpoint=~"^/home($|/)|^/srv($|/)|^/data($|/)|^/mnt($|/)|^/media($|/)",mountpoint!~"^/(boot|boot/efi|efi|var|usr|opt|run|dev|proc|sys)($|/)"';
const SYSTEM_MOUNT_FILTER =
'mountpoint=~"^/$|^/boot($|/)|^/boot/efi$|^/var($|/)|^/usr($|/)|^/opt($|/)",mountpoint!~"^/home($|/)|^/srv($|/)|^/data($|/)|^/mnt($|/)|^/media($|/)"';
const diskUsedQuery = (mountFilter: string) => {
const selector = `job="node",${REAL_FILESYSTEM_FILTER},${mountFilter}`;
return `topk(1, max by (host, mountpoint) (100 * (1 - node_filesystem_avail_bytes{${selector}} / node_filesystem_size_bytes{${selector}})))`;
};
const ramQuery = (host: string) =>
`100 * (1 - node_memory_MemAvailable_bytes{job="node",host="${host}"} / node_memory_MemTotal_bytes{job="node",host="${host}"})`;
const gpuQuery = (name: string, metric: string) =>
`${metric}{job="node",name=~".*${name}.*"}`;
const gpuVramQuery = (name: string) =>
`100 * nvidia_gpu_memory_used_bytes{job="node",name=~".*${name}.*"} / nvidia_gpu_memory_total_bytes{job="node",name=~".*${name}.*"}`;
const hostCpuQuery =
'topk(1, 100 * (1 - avg by (host) (rate(node_cpu_seconds_total{job="node",mode="idle"}[5m]))))';
const topCpuQuery =
'topk(1, 100 * rate(podman_container_cpu_seconds_total{job=~"podman-.*"}[5m]) * on(host,id) group_left(name) podman_container_info{job=~"podman-.*"})';
const topRamQuery =
'topk(1, podman_container_mem_usage_bytes{job=~"podman-.*"} * on(host,id) group_left(name) podman_container_info{job=~"podman-.*"})';
export const dimensionLabDashboardFixture: DashboardDocument = {
schemaVersion: DASHBOARD_SCHEMA_VERSION,
metadata: {
title: "System Overview",
subtitle: "Capacity, noise & top consumers",
description:
"Initial Dimension Lab dashboard seed data. Runtime values are fallback values until datasource adapters are enabled.",
timezone: "Europe/Amsterdam",
refreshIntervalSeconds: 15,
},
layout: {
density: "dense",
telemetry: [
"infra-ram",
"gpu-host-ram",
"network-ram",
"user-disk-peak",
"system-disk-peak",
"peak-cpu-busy",
"top-cpu-container",
"top-ram-container",
"gpu-3060-load",
"gpu-3060-vram",
"gpu-3060-temp",
"gpu-3060-fan",
"gpu-3090-load",
"gpu-3090-vram",
"gpu-3090-temp",
"gpu-3090-fan",
],
serviceGroups: ["essentials", "monitoring", "ai-automation", "systems", "runtime-health"],
statusStrips: ["footer-status"],
modules: ["weather-amsterdam", "runtime-health-summary"],
},
telemetry: [
metric({
id: "infra-ram",
label: "Infra RAM",
icon: "mdi:memory",
kind: "percent",
value: 19,
detail: "fallback - linux-infra memory used",
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
ramQuery("linux-infra"),
),
sparkline: [18, 18, 19, 19, 18, 19],
}),
metric({
id: "gpu-host-ram",
label: "GPU Host RAM",
icon: "mdi:memory",
kind: "percent",
value: 17,
detail: "fallback - GPU host memory used",
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
ramQuery("linux"),
),
sparkline: [16, 16, 17, 17, 17, 17],
}),
metric({
id: "network-ram",
label: "Network RAM",
icon: "mdi:router-network",
kind: "percent",
value: 5,
detail: "fallback - network core memory used",
severity: "ok",
thresholds: percentThresholds(),
datasource: prometheus(
ramQuery("network-core"),
),
sparkline: [5, 5, 5, 6, 5, 5],
}),
metric({
id: "user-disk-peak",
label: "User Disk Peak",
icon: "mdi:harddisk",
kind: "percent",
value: 29,
detail: "fallback - /home peak usage",
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
diskUsedQuery(USER_MOUNT_FILTER),
),
sparkline: [27, 27, 28, 29, 29, 29],
}),
metric({
id: "system-disk-peak",
label: "System Disk Peak",
icon: "mdi:harddisk",
kind: "percent",
value: 49,
detail: "fallback - /boot peak usage",
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
diskUsedQuery(SYSTEM_MOUNT_FILTER),
),
sparkline: [48, 48, 49, 49, 49, 49],
}),
metric({
id: "peak-cpu-busy",
label: "Peak CPU Busy",
icon: "mdi:cpu-64-bit",
kind: "percent",
value: 3,
detail: "fallback - linux-infra 5m CPU busy",
severity: "ok",
thresholds: percentThresholds(75, 90),
datasource: prometheus(
hostCpuQuery,
),
sparkline: [2, 3, 3, 4, 3, 3],
}),
metric({
id: "top-cpu-container",
label: "Top CPU Container",
icon: "mdi:docker",
kind: "percent",
value: 7.9,
precision: 1,
detail: "fallback - top container CPU pending label mapping",
severity: "ok",
thresholds: percentThresholds(70, 90),
datasource: prometheus(topCpuQuery),
sparkline: [6.1, 6.4, 7.0, 7.5, 7.2, 7.9],
}),
metric({
id: "top-ram-container",
label: "Top RAM Container",
icon: "mdi:docker",
kind: "bytes",
value: Math.round(5.2 * 1024 ** 3),
precision: 1,
detail: "fallback - top container RAM pending label mapping",
severity: "danger",
thresholds: { warning: 3 * 1024 ** 3, danger: 5 * 1024 ** 3 },
datasource: prometheus(topRamQuery),
sparkline: [
3.1 * 1024 ** 3,
3.5 * 1024 ** 3,
4.1 * 1024 ** 3,
4.8 * 1024 ** 3,
5.0 * 1024 ** 3,
5.2 * 1024 ** 3,
],
}),
metric({
id: "gpu-3060-load",
label: "3060 GPU Load",
icon: "mdi:expansion-card",
kind: "percent",
value: 0,
detail: "fallback - RTX 3060 GPU utilization",
severity: "ok",
thresholds: percentThresholds(85, 95),
datasource: prometheus(gpuQuery("3060", "nvidia_gpu_utilization_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3060-vram",
label: "3060 VRAM",
icon: "mdi:memory",
kind: "percent",
value: 0,
detail: "fallback - RTX 3060 VRAM used",
severity: "ok",
thresholds: percentThresholds(80, 92),
datasource: prometheus(
gpuVramQuery("3060"),
),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3060-temp",
label: "3060 Temp",
icon: "mdi:thermometer",
kind: "temperature",
value: 54,
detail: "fallback - RTX 3060 temperature",
severity: "ok",
thresholds: { warning: 75, danger: 85 },
datasource: prometheus(gpuQuery("3060", "nvidia_gpu_temperature_celsius")),
sparkline: [52, 53, 54, 54, 53, 54],
}),
metric({
id: "gpu-3060-fan",
label: "3060 Fan Spin",
icon: "mdi:fan",
kind: "percent",
value: 0,
detail: "fallback - RTX 3060 fan duty",
severity: "ok",
thresholds: percentThresholds(80, 95),
datasource: prometheus(gpuQuery("3060", "nvidia_gpu_fan_speed_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3090-load",
label: "3090 GPU Load",
icon: "mdi:expansion-card",
kind: "percent",
value: 0,
detail: "fallback - RTX 3090 GPU utilization",
severity: "ok",
thresholds: percentThresholds(85, 95),
datasource: prometheus(gpuQuery("3090", "nvidia_gpu_utilization_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
metric({
id: "gpu-3090-vram",
label: "3090 VRAM",
icon: "mdi:memory",
kind: "percent",
value: 74,
detail: "fallback - RTX 3090 VRAM used",
severity: "warning",
thresholds: percentThresholds(70, 90),
datasource: prometheus(
gpuVramQuery("3090"),
),
sparkline: [68, 70, 72, 74, 73, 74],
}),
metric({
id: "gpu-3090-temp",
label: "3090 Temp",
icon: "mdi:thermometer",
kind: "temperature",
value: 50,
detail: "fallback - RTX 3090 temperature",
severity: "ok",
thresholds: { warning: 75, danger: 85 },
datasource: prometheus(gpuQuery("3090", "nvidia_gpu_temperature_celsius")),
sparkline: [49, 50, 50, 51, 50, 50],
}),
metric({
id: "gpu-3090-fan",
label: "3090 Fan Spin",
icon: "mdi:fan",
kind: "percent",
value: 0,
detail: "fallback - RTX 3090 fan duty",
severity: "ok",
thresholds: percentThresholds(80, 95),
datasource: prometheus(gpuQuery("3090", "nvidia_gpu_fan_speed_percent")),
sparkline: [0, 0, 0, 0, 0, 0],
}),
],
serviceGroups: [
group("essentials", "Essentials", [
service({
id: "vaultwarden",
label: "Vaultwarden",
description: "Password manager",
icon: "simple-icons:vaultwarden",
href: "https://vault.dimensionlab.net",
datasource: uptimeMonitor(1),
}),
service({
id: "forgejo",
label: "Forgejo",
description: "Git repositories",
icon: "simple-icons:forgejo",
href: "https://git.dimensionlab.net",
datasource: uptimeMonitor(2),
}),
service({
id: "wiki",
label: "Wiki",
description: "Internal documentation",
icon: "simple-icons:wikidotjs",
href: "https://wiki.dimensionlab.net",
datasource: uptimeMonitor(3),
}),
service({
id: "aws-start",
label: "AWS Start",
description: "AWS access portal",
icon: "simple-icons:amazonwebservices",
href: "https://dimensionlab.awsapps.com/start",
datasource: uptimeMonitor(21),
}),
service({
id: "adguard-primary",
label: "AdGuard Primary",
description: "DNS filtering and DHCP",
icon: "simple-icons:adguard",
href: "https://control.dimensionlab.net",
datasource: uptimeMonitor(14),
}),
service({
id: "adguard-secondary",
label: "AdGuard Secondary",
description: "Fallback DNS",
icon: "simple-icons:adguard",
href: "https://control-secondary.dimensionlab.net",
datasource: uptimeMonitor(18),
}),
]),
group("monitoring", "Monitoring", [
service({
id: "grafana",
label: "Grafana",
description: "Capacity and noise dashboard",
icon: "simple-icons:grafana",
href: "https://grafana.dimensionlab.net",
datasource: uptimeMonitor(11),
}),
service({
id: "uptime-kuma",
label: "Uptime Kuma",
description: "Service uptime checks",
icon: "simple-icons:uptimekuma",
href: "https://uptime.dimensionlab.net",
datasource: uptimeMonitor(10),
}),
service({
id: "prometheus",
label: "Prometheus",
description: "Metrics database",
icon: "simple-icons:prometheus",
href: "https://prometheus.dimensionlab.net",
datasource: uptimeMonitor(12),
}),
service({
id: "backrest",
label: "Backrest",
description: "Restic backup manager",
icon: "mdi:backup-restore",
href: "https://backups.dimensionlab.net",
datasource: uptimeMonitor(13),
}),
]),
group("ai-automation", "AI & Automation", [
service({
id: "n8n",
label: "n8n",
description: "Workflow automation",
icon: "simple-icons:n8n",
href: "https://workflows.dimensionlab.net",
datasource: uptimeMonitor(4),
}),
service({
id: "open-webui",
label: "Open WebUI",
description: "Chat and model interface",
icon: "mdi:web",
href: "https://webui.dimensionlab.net",
datasource: uptimeMonitor(5),
}),
service({
id: "comfyui",
label: "ComfyUI",
description: "Image generation workflows",
icon: "mdi:image-edit-outline",
href: "https://comfy.dimensionlab.net",
datasource: uptimeMonitor(6),
}),
service({
id: "models",
label: "Models",
description: "Local model management",
icon: "mdi:brain",
href: "https://models.dimensionlab.net",
datasource: uptimeMonitor(7),
}),
service({
id: "prompt-registry",
label: "Prompt Registry",
description: "Shared prompts, traces, evals",
icon: "mdi:text-box-search",
href: "https://prompts.dimensionlab.net",
datasource: uptimeMonitor(20),
}),
]),
group("systems", "Systems", [
service({
id: "adminer",
label: "Adminer",
description: "PostgreSQL database browser",
icon: "simple-icons:adminer",
href: "https://db.dimensionlab.net",
datasource: uptimeMonitor(17),
}),
service({
id: "assistant",
label: "Assistant",
description: "Personal AI agent gateway",
icon: "mdi:robot-outline",
href: "https://assistant.dimensionlab.net",
datasource: uptimeMonitor(8),
}),
service({
id: "suna",
label: "Suna",
description: "AI command center",
icon: "mdi:account-hard-hat-outline",
href: "https://suna.dimensionlab.net",
datasource: uptimeMonitor(9),
}),
service({
id: "cockpit-infra",
label: "Cockpit Infra",
description: "linux-infra server console",
icon: "simple-icons:cockpit",
href: "https://infra-cockpit.dimensionlab.net",
datasource: uptimeMonitor(15),
}),
service({
id: "cockpit-gpu",
label: "Cockpit GPU",
description: "Linux GPU server console",
icon: "simple-icons:cockpit",
href: "https://linux-cockpit.dimensionlab.net",
datasource: uptimeMonitor(16),
}),
service({
id: "cockpit-network-core",
label: "Cockpit Network Core",
description: "i3 NUC DNS/DHCP console",
icon: "simple-icons:cockpit",
href: "https://network-core.dimensionlab.net",
datasource: uptimeMonitor(19),
}),
]),
group("runtime-health", "Runtime Health", [
service({
id: "forgejo-ssh-relay",
label: "Forgejo SSH Relay",
description: "Public Git SSH relay",
icon: "simple-icons:forgejo",
href: "https://uptime.dimensionlab.net/status/dimensionlab",
datasource: uptimeMonitor(28),
detail: "fallback - uptime monitor pending",
}),
service({
id: "postgresql",
label: "PostgreSQL",
description: "Shared application database",
icon: "simple-icons:postgresql",
datasource: uptimeMonitor(22),
detail: "fallback - postgres exporter pending",
}),
service({
id: "ollama-api",
label: "Ollama API",
description: "Local model API",
icon: "simple-icons:ollama",
datasource: uptimeMonitor(23),
detail: "fallback - internal health check pending",
}),
service({
id: "node-exporter",
label: "Node Exporter",
description: "Host metrics exporter",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(24),
detail: "fallback - exporter health from Uptime Kuma",
}),
service({
id: "podman-user-exporter",
label: "Podman User Exporter",
description: "Rootless container metrics",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(25),
detail: "fallback - exporter health from Uptime Kuma",
}),
service({
id: "podman-system-exporter",
label: "Podman System Exporter",
description: "System container metrics",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(26),
detail: "fallback - exporter health from Uptime Kuma",
}),
service({
id: "network-core-node-exporter",
label: "Network Core Node Exporter",
description: "i3 DNS/DHCP metrics",
icon: "simple-icons:prometheus",
datasource: uptimeMonitor(27),
detail: "fallback - exporter health from Prometheus",
}),
], "grid"),
],
statusStrips: [
{
id: "footer-status",
items: [
{ id: "system-status", label: "System Status", value: "Fallback operational", severity: "ok" },
{ id: "last-sync", label: "Last Sync", value: "fallback: 2 minutes ago", severity: "stale" },
{ id: "uptime", label: "Uptime", value: "fallback: 28d 14h 32m", severity: "ok" },
{ id: "load-avg", label: "Load Avg", value: "fallback: 0.47 0.53 0.59", severity: "neutral" },
{ id: "auto-refresh", label: "Auto Refresh", value: "fallback: 15s", severity: "neutral" },
],
},
],
modules: [
{
id: "weather-amsterdam",
kind: "weather",
title: "Amsterdam",
value: "28.3 C",
detail: "fallback - weather adapter pending",
icon: "mdi:weather-sunny",
severity: "ok",
datasource: {
type: "external",
adapter: "weather",
reference: "open-meteo:latitude=52.3676&longitude=4.9041",
},
},
{
id: "runtime-health-summary",
kind: "summary",
title: "Runtime Health",
value: "fallback",
detail: "fallback - health adapters pending",
icon: "mdi:pulse",
severity: "stale",
datasource: placeholder("summary pending live health aggregation"),
},
],
};
function metric(seed: MetricSeed): TelemetryCard {
return {
id: seed.id,
label: seed.label,
icon: seed.icon,
value: metricValue(seed.kind, seed.value, seed.precision),
detail: seed.detail,
severity: seed.severity,
thresholds: seed.thresholds,
datasource: seed.datasource,
sparkline: seed.sparkline,
};
}
function metricValue(
kind: NumericValueKind,
value: number,
precision?: number,
): MetricValue {
return precision === undefined ? { kind, value } : { kind, value, precision };
}
function service(seed: ServiceSeed): ServiceEntry {
const entry: ServiceEntry = {
id: seed.id,
label: seed.label,
description: seed.description,
icon: seed.icon,
severity: seed.severity || "ok",
detail: seed.detail || "fallback - health check pending",
datasource: seed.datasource,
};
if (!seed.href) return entry;
return {
...entry,
link: {
href: seed.href,
label: `Open ${seed.label}`,
external: true,
},
};
}
function group(
id: string,
title: string,
services: ServiceEntry[],
layout: ServiceGroup["layout"] = "list",
): ServiceGroup {
return {
id,
layout,
services,
title,
};
}
function percentThresholds(warning = 80, danger = 92) {
return { warning, danger };
}
function prometheus(reference: string): DatasourceReference {
return {
type: "external",
adapter: "prometheus",
reference,
};
}
function httpStatus(url: string): DatasourceReference {
return {
type: "external",
adapter: "http-status",
reference: `GET ${url}`,
};
}
function placeholder(reason: string): DatasourceReference {
return {
type: "placeholder",
reason,
};
}
function uptimeMonitor(id: number): DatasourceReference {
return httpStatus(`https://uptime.dimensionlab.net/_homepage-badge/${id}`);
}

View file

@ -0,0 +1,91 @@
import {
DASHBOARD_SCHEMA_VERSION,
type DashboardDocument,
} from "../schema";
export const genericDashboardFixture: DashboardDocument = {
schemaVersion: DASHBOARD_SCHEMA_VERSION,
metadata: {
title: "Operations Console",
subtitle: "Generic environment",
description: "Portable fixture for component and renderer tests.",
timezone: "UTC",
refreshIntervalSeconds: 30,
},
layout: {
density: "dense",
telemetry: ["service-uptime", "queue-depth"],
serviceGroups: ["core-services"],
statusStrips: ["runtime"],
modules: ["ambient"],
},
telemetry: [
{
id: "service-uptime",
label: "Service Uptime",
value: { kind: "percent", value: 99.9, precision: 1 },
detail: "last 30 days",
severity: "ok",
datasource: { type: "static", label: "fixture" },
sparkline: [99.7, 99.8, 99.9, 99.9],
},
{
id: "queue-depth",
label: "Queue Depth",
value: { kind: "number", value: 18 },
detail: "pending jobs",
severity: "warning",
thresholds: { warning: 15, danger: 50 },
datasource: { type: "placeholder", reason: "adapter pending" },
sparkline: [6, 11, 13, 18],
},
],
serviceGroups: [
{
id: "core-services",
title: "Core Services",
layout: "list",
services: [
{
id: "identity",
label: "Identity",
description: "Authentication and profile service",
icon: "mdi:account-key-outline",
severity: "ok",
detail: "ready",
datasource: { type: "static", label: "fixture" },
},
{
id: "scheduler",
label: "Scheduler",
description: "Background task coordinator",
icon: "mdi:calendar-clock",
severity: "warning",
detail: "delayed",
datasource: { type: "placeholder", reason: "health adapter pending" },
},
],
},
],
statusStrips: [
{
id: "runtime",
items: [
{ id: "status", label: "System Status", value: "Degraded", severity: "warning" },
{ id: "sync", label: "Last Sync", value: "2 minutes ago", severity: "stale" },
],
},
],
modules: [
{
id: "ambient",
kind: "summary",
title: "Environment",
value: "Nominal",
detail: "static fixture",
icon: "mdi:radar",
severity: "ok",
datasource: { type: "static", label: "fixture" },
},
],
};

View file

@ -0,0 +1,2 @@
export { dimensionLabDashboardFixture } from "./dimensionlab";
export { genericDashboardFixture } from "./generic";

View file

@ -0,0 +1,29 @@
export {
DASHBOARD_SCHEMA_VERSION,
DashboardDocumentSchema,
DatasourceReferenceSchema,
ServiceEntrySchema,
ServiceGroupSchema,
TelemetryCardSchema,
ThresholdSchema,
dashboardDocumentJsonSchema,
type DashboardDocument,
type DashboardModule,
type DatasourceReference,
type MetricValue,
type ServiceEntry,
type ServiceGroup,
type Severity,
type StatusItem,
type StatusStrip,
type TelemetryCard,
} from "./schema";
export {
assertDashboardDocument,
formatValidationErrors,
isDashboardDocument,
validateDashboardDocument,
type DashboardValidationFailure,
type DashboardValidationResult,
type DashboardValidationSuccess,
} from "./validation";

View file

@ -0,0 +1,204 @@
import { describe, expect, it } from "vitest";
import {
DASHBOARD_SCHEMA_VERSION,
dashboardDocumentJsonSchema,
validateDashboardDocument,
} from ".";
import {
dimensionLabDashboardFixture,
genericDashboardFixture,
} from "./fixtures";
describe("dashboard model validation", () => {
it("accepts the generic dashboard fixture", () => {
const result = validateDashboardDocument(genericDashboardFixture);
expect(result.valid).toBe(true);
if (result.valid) {
expect(result.data.schemaVersion).toBe(DASHBOARD_SCHEMA_VERSION);
}
});
it("accepts the Dimension Lab dashboard fixture", () => {
const result = validateDashboardDocument(dimensionLabDashboardFixture);
expect(result.valid).toBe(true);
});
it("rejects documents with an unsupported schema version", () => {
const invalid = {
...genericDashboardFixture,
schemaVersion: "dashboard.v0",
};
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be equal to constant");
expect(result.details.length).toBeGreaterThan(0);
}
});
it("returns actionable field paths for invalid documents", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
delete invalid.metadata.title;
invalid.telemetry[0].severity = "fine";
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.some((error) => error.includes("/metadata"))).toBe(true);
expect(result.errors.some((error) => error.includes("/telemetry/0/severity"))).toBe(true);
}
});
it("includes offending additional property names in errors", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.metadata.unexpected = true;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("unexpected");
}
});
it("rejects non-finite numbers that cannot roundtrip through JSON", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value.value = Number.NaN;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("finite JSON number");
}
});
it("rejects dangling layout references", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.layout.telemetry.push("missing-card");
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("missing-card");
expect(result.errors.join(" ")).toContain("must reference an existing item");
}
});
it("rejects duplicate IDs within collections", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[1].id = invalid.telemetry[0].id;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be unique");
expect(result.errors.join(" ")).toContain(invalid.telemetry[0].id);
}
});
it("rejects duplicate status strip item IDs", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.statusStrips[0].items.push({
...invalid.statusStrips[0].items[0],
});
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be unique");
expect(result.errors.join(" ")).toContain(invalid.statusStrips[0].items[0].id);
}
});
it("rejects string values for numeric metric kinds", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value.value = "not a percent";
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
});
it("rejects percent values outside 0 to 100", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value.value = 150;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
});
it("rejects negative values for nonnegative metric kinds", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value = { kind: "latency", value: -20 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
});
it("rejects contradictory warning and danger thresholds", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].thresholds = { warning: 90, danger: 80 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("warning threshold");
}
});
it("rejects percent thresholds above 100", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].thresholds = { warning: 99, danger: 999 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("percent thresholds");
}
});
it("rejects thresholds on text metric values", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.telemetry[0].value = { kind: "text", value: "available" };
invalid.telemetry[0].thresholds = { warning: 10 };
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("text metric values");
}
});
it("rejects undefined properties because they are not JSON values", () => {
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
invalid.serviceGroups[0].services[0].link = undefined;
const result = validateDashboardDocument(invalid);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.join(" ")).toContain("must be omitted instead of undefined");
}
});
it("exports JSON Schema for external tool contracts", () => {
expect(dashboardDocumentJsonSchema.$id).toContain("dashboard-document.v1");
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("schemaVersion");
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("telemetry");
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("serviceGroups");
});
});

View file

@ -0,0 +1,252 @@
import { Type, type Static } from "@sinclair/typebox";
export const DASHBOARD_SCHEMA_VERSION = "dashboard.v1" as const;
const IdentifierSchema = Type.String({
minLength: 1,
pattern: "^[a-z0-9][a-z0-9-_.:]*$",
});
const SeveritySchema = Type.Union([
Type.Literal("neutral"),
Type.Literal("ok"),
Type.Literal("warning"),
Type.Literal("danger"),
Type.Literal("stale"),
Type.Literal("unavailable"),
]);
const IconReferenceSchema = Type.String({ minLength: 1 });
const LinkSchema = Type.Object(
{
href: Type.String({ format: "uri" }),
label: Type.Optional(Type.String({ minLength: 1 })),
external: Type.Optional(Type.Boolean()),
},
{ additionalProperties: false },
);
export const StaticDatasourceSchema = Type.Object(
{
type: Type.Literal("static"),
label: Type.Optional(Type.String({ minLength: 1 })),
updatedAt: Type.Optional(Type.String({ format: "date-time" })),
},
{ additionalProperties: false },
);
export const PlaceholderDatasourceSchema = Type.Object(
{
type: Type.Literal("placeholder"),
reason: Type.String({ minLength: 1 }),
},
{ additionalProperties: false },
);
export const ExternalDatasourceSchema = Type.Object(
{
type: Type.Literal("external"),
adapter: Type.Union([
Type.Literal("prometheus"),
Type.Literal("http-status"),
Type.Literal("weather"),
Type.Literal("custom"),
]),
reference: Type.String({ minLength: 1 }),
},
{ additionalProperties: false },
);
export const DatasourceReferenceSchema = Type.Union([
StaticDatasourceSchema,
PlaceholderDatasourceSchema,
ExternalDatasourceSchema,
]);
const PercentMetricValueSchema = Type.Object(
{
kind: Type.Literal("percent"),
value: Type.Number({ minimum: 0, maximum: 100 }),
unit: Type.Optional(Type.String({ minLength: 1 })),
precision: Type.Optional(Type.Integer({ minimum: 0, maximum: 4 })),
},
{ additionalProperties: false },
);
const NonNegativeMetricValueSchema = Type.Object(
{
kind: Type.Union([
Type.Literal("bytes"),
Type.Literal("temperature"),
Type.Literal("latency"),
Type.Literal("number"),
]),
value: Type.Number({ minimum: 0 }),
unit: Type.Optional(Type.String({ minLength: 1 })),
precision: Type.Optional(Type.Integer({ minimum: 0, maximum: 4 })),
},
{ additionalProperties: false },
);
const TextMetricValueSchema = Type.Object(
{
kind: Type.Literal("text"),
value: Type.String(),
unit: Type.Optional(Type.String({ minLength: 1 })),
},
{ additionalProperties: false },
);
export const MetricValueSchema = Type.Union([
PercentMetricValueSchema,
NonNegativeMetricValueSchema,
TextMetricValueSchema,
]);
export const ThresholdSchema = Type.Object(
{
warning: Type.Optional(Type.Number({ minimum: 0 })),
danger: Type.Optional(Type.Number({ minimum: 0 })),
},
{ additionalProperties: false, minProperties: 1 },
);
export const TelemetryCardSchema = Type.Object(
{
id: IdentifierSchema,
label: Type.String({ minLength: 1 }),
description: Type.Optional(Type.String()),
icon: Type.Optional(IconReferenceSchema),
value: MetricValueSchema,
detail: Type.Optional(Type.String()),
severity: SeveritySchema,
thresholds: Type.Optional(ThresholdSchema),
datasource: Type.Optional(DatasourceReferenceSchema),
sparkline: Type.Optional(Type.Array(Type.Number())),
},
{ additionalProperties: false },
);
export const ServiceEntrySchema = Type.Object(
{
id: IdentifierSchema,
label: Type.String({ minLength: 1 }),
description: Type.String(),
icon: Type.Optional(IconReferenceSchema),
link: Type.Optional(LinkSchema),
severity: SeveritySchema,
detail: Type.Optional(Type.String()),
datasource: Type.Optional(DatasourceReferenceSchema),
},
{ additionalProperties: false },
);
export const ServiceGroupSchema = Type.Object(
{
id: IdentifierSchema,
title: Type.String({ minLength: 1 }),
layout: Type.Optional(Type.Union([Type.Literal("list"), Type.Literal("grid")])),
services: Type.Array(ServiceEntrySchema),
},
{ additionalProperties: false },
);
export const StatusItemSchema = Type.Object(
{
id: IdentifierSchema,
label: Type.String({ minLength: 1 }),
value: Type.String(),
severity: Type.Optional(SeveritySchema),
link: Type.Optional(LinkSchema),
},
{ additionalProperties: false },
);
export const StatusStripSchema = Type.Object(
{
id: IdentifierSchema,
title: Type.Optional(Type.String({ minLength: 1 })),
items: Type.Array(StatusItemSchema),
},
{ additionalProperties: false },
);
export const DashboardModuleSchema = Type.Object(
{
id: IdentifierSchema,
kind: Type.Union([
Type.Literal("summary"),
Type.Literal("weather"),
Type.Literal("custom"),
]),
title: Type.Optional(Type.String({ minLength: 1 })),
label: Type.Optional(Type.String()),
value: Type.Optional(Type.String()),
detail: Type.Optional(Type.String()),
icon: Type.Optional(IconReferenceSchema),
severity: Type.Optional(SeveritySchema),
datasource: Type.Optional(DatasourceReferenceSchema),
},
{ additionalProperties: false },
);
const LayoutSchema = Type.Object(
{
density: Type.Optional(Type.Union([Type.Literal("compact"), Type.Literal("dense")])),
telemetry: Type.Array(IdentifierSchema),
serviceGroups: Type.Array(IdentifierSchema),
statusStrips: Type.Array(IdentifierSchema),
modules: Type.Optional(Type.Array(IdentifierSchema)),
},
{ additionalProperties: false },
);
const MetadataSchema = Type.Object(
{
title: Type.String({ minLength: 1 }),
subtitle: Type.Optional(Type.String()),
description: Type.Optional(Type.String()),
timezone: Type.Optional(Type.String({ minLength: 1 })),
refreshIntervalSeconds: Type.Optional(Type.Integer({ minimum: 5 })),
},
{ additionalProperties: false },
);
export const DashboardDocumentSchema = Type.Object(
{
schemaVersion: Type.Literal(DASHBOARD_SCHEMA_VERSION),
metadata: MetadataSchema,
layout: LayoutSchema,
telemetry: Type.Array(TelemetryCardSchema),
serviceGroups: Type.Array(ServiceGroupSchema),
statusStrips: Type.Array(StatusStripSchema),
modules: Type.Optional(Type.Array(DashboardModuleSchema)),
migration: Type.Optional(
Type.Object(
{
previousVersion: Type.Optional(Type.String({ minLength: 1 })),
notes: Type.Optional(Type.String()),
},
{ additionalProperties: false },
),
),
},
{
$id: "https://dimensionlab.net/schemas/dashboard-document.v1.json",
additionalProperties: false,
},
);
export type Severity = Static<typeof SeveritySchema>;
export type DatasourceReference = Static<typeof DatasourceReferenceSchema>;
export type MetricValue = Static<typeof MetricValueSchema>;
export type TelemetryCard = Static<typeof TelemetryCardSchema>;
export type ServiceEntry = Static<typeof ServiceEntrySchema>;
export type ServiceGroup = Static<typeof ServiceGroupSchema>;
export type StatusItem = Static<typeof StatusItemSchema>;
export type StatusStrip = Static<typeof StatusStripSchema>;
export type DashboardModule = Static<typeof DashboardModuleSchema>;
export type DashboardDocument = Static<typeof DashboardDocumentSchema>;
export const dashboardDocumentJsonSchema = DashboardDocumentSchema;

View file

@ -0,0 +1,322 @@
import Ajv, { type ErrorObject } from "ajv";
import addFormats from "ajv-formats";
import {
DashboardDocumentSchema,
type DashboardDocument,
} from "./schema";
type DashboardValidationIssue = ErrorObject | SemanticValidationIssue;
interface SemanticValidationIssue {
instancePath: string;
schemaPath: string;
keyword: "semantic";
params: Record<string, unknown>;
message: string;
}
export interface DashboardValidationFailure {
valid: false;
errors: string[];
details: DashboardValidationIssue[];
}
export interface DashboardValidationSuccess {
valid: true;
data: DashboardDocument;
}
export type DashboardValidationResult =
| DashboardValidationFailure
| DashboardValidationSuccess;
const ajv = addFormats(
new Ajv({
allErrors: true,
strict: false,
strictNumbers: true,
}),
);
const validateDashboard = ajv.compile<DashboardDocument>(DashboardDocumentSchema);
export function formatValidationErrors(
errors: DashboardValidationIssue[] = [],
): string[] {
return errors.map((error) => {
const path = error.instancePath || "/";
const suffix = formatErrorParams(error);
const message = error.message || "is invalid";
return `${path} ${message}${suffix}`;
});
}
export function validateDashboardDocument(
value: unknown,
): DashboardValidationResult {
const finiteNumberIssues: SemanticValidationIssue[] = [];
const undefinedIssues: SemanticValidationIssue[] = [];
collectFiniteNumberIssues(value, "", finiteNumberIssues);
collectUndefinedIssues(value, "", undefinedIssues);
if (validateDashboard(value)) {
const semanticIssues = [
...finiteNumberIssues,
...undefinedIssues,
...validateSemanticRules(value),
];
if (semanticIssues.length === 0) {
return { valid: true, data: value };
}
return {
valid: false,
errors: formatValidationErrors(semanticIssues),
details: semanticIssues,
};
}
const details = [
...(validateDashboard.errors || []),
...finiteNumberIssues,
...undefinedIssues,
];
return {
valid: false,
errors: formatValidationErrors(details),
details,
};
}
export function assertDashboardDocument(
value: unknown,
): asserts value is DashboardDocument {
const result = validateDashboardDocument(value);
if (!result.valid) {
throw new Error(`Invalid dashboard document: ${result.errors.join("; ")}`);
}
}
export function isDashboardDocument(value: unknown): value is DashboardDocument {
return validateDashboardDocument(value).valid;
}
function formatErrorParams(error: DashboardValidationIssue): string {
if (error.keyword === "additionalProperties") {
const additionalProperty = error.params.additionalProperty;
return typeof additionalProperty === "string"
? `: ${additionalProperty}`
: "";
}
if (error.keyword === "semantic") {
const id = error.params.id;
const ref = error.params.ref;
if (typeof id === "string") return `: ${id}`;
if (typeof ref === "string") return `: ${ref}`;
}
return "";
}
function validateSemanticRules(document: DashboardDocument): SemanticValidationIssue[] {
const issues: SemanticValidationIssue[] = [];
collectDuplicateIdIssues("telemetry", document.telemetry, issues);
collectDuplicateIdIssues("serviceGroups", document.serviceGroups, issues);
collectDuplicateIdIssues("statusStrips", document.statusStrips, issues);
collectDuplicateIdIssues("modules", document.modules || [], issues);
document.serviceGroups.forEach((group, groupIndex) => {
collectDuplicateIdIssues(
`serviceGroups/${groupIndex}/services`,
group.services,
issues,
);
});
document.statusStrips.forEach((strip, stripIndex) => {
collectDuplicateIdIssues(
`statusStrips/${stripIndex}/items`,
strip.items,
issues,
);
});
collectMissingReferenceIssues(
"layout/telemetry",
document.layout.telemetry,
new Set(document.telemetry.map((item) => item.id)),
issues,
);
collectMissingReferenceIssues(
"layout/serviceGroups",
document.layout.serviceGroups,
new Set(document.serviceGroups.map((item) => item.id)),
issues,
);
collectMissingReferenceIssues(
"layout/statusStrips",
document.layout.statusStrips,
new Set(document.statusStrips.map((item) => item.id)),
issues,
);
collectMissingReferenceIssues(
"layout/modules",
document.layout.modules || [],
new Set((document.modules || []).map((item) => item.id)),
issues,
);
document.telemetry.forEach((card, index) => {
if (card.value.kind === "text" && card.thresholds !== undefined) {
issues.push(
semanticIssue(
`/telemetry/${index}/thresholds`,
"must not be set for text metric values",
{ id: card.id },
),
);
}
if (
card.value.kind === "percent" &&
((card.thresholds?.warning !== undefined && card.thresholds.warning > 100) ||
(card.thresholds?.danger !== undefined && card.thresholds.danger > 100))
) {
issues.push(
semanticIssue(
`/telemetry/${index}/thresholds`,
"percent thresholds must be between 0 and 100",
{ id: card.id },
),
);
}
const warning = card.thresholds?.warning;
const danger = card.thresholds?.danger;
if (warning !== undefined && danger !== undefined && warning > danger) {
issues.push(
semanticIssue(
`/telemetry/${index}/thresholds`,
"warning threshold must be less than or equal to danger threshold",
{ id: card.id },
),
);
}
});
return issues;
}
function collectUndefinedIssues(
value: unknown,
path: string,
issues: SemanticValidationIssue[],
) {
if (value === undefined) {
issues.push(
semanticIssue(path || "/", "must be omitted instead of undefined", {}),
);
return;
}
if (Array.isArray(value)) {
value.forEach((item, index) => {
collectUndefinedIssues(item, `${path}/${index}`, issues);
});
return;
}
if (value && typeof value === "object") {
Object.entries(value).forEach(([key, item]) => {
collectUndefinedIssues(item, `${path}/${escapeJsonPointer(key)}`, issues);
});
}
}
function collectFiniteNumberIssues(
value: unknown,
path: string,
issues: SemanticValidationIssue[],
) {
if (typeof value === "number") {
if (!Number.isFinite(value)) {
issues.push(
semanticIssue(path || "/", "must be a finite JSON number", {}),
);
}
return;
}
if (Array.isArray(value)) {
value.forEach((item, index) => {
collectFiniteNumberIssues(item, `${path}/${index}`, issues);
});
return;
}
if (value && typeof value === "object") {
Object.entries(value).forEach(([key, item]) => {
collectFiniteNumberIssues(item, `${path}/${escapeJsonPointer(key)}`, issues);
});
}
}
function collectDuplicateIdIssues(
collectionPath: string,
items: Array<{ id: string }>,
issues: SemanticValidationIssue[],
) {
const seen = new Set<string>();
items.forEach((item, index) => {
if (seen.has(item.id)) {
issues.push(
semanticIssue(
`/${collectionPath}/${index}/id`,
"must be unique within its collection",
{ id: item.id },
),
);
return;
}
seen.add(item.id);
});
}
function collectMissingReferenceIssues(
layoutPath: string,
refs: string[],
validIds: Set<string>,
issues: SemanticValidationIssue[],
) {
refs.forEach((ref, index) => {
if (!validIds.has(ref)) {
issues.push(
semanticIssue(
`/${layoutPath}/${index}`,
"must reference an existing item",
{ ref },
),
);
}
});
}
function semanticIssue(
instancePath: string,
message: string,
params: Record<string, unknown>,
): SemanticValidationIssue {
return {
instancePath,
schemaPath: "",
keyword: "semantic",
params,
message,
};
}
function escapeJsonPointer(value: string): string {
return value.replaceAll("~", "~0").replaceAll("/", "~1");
}

View file

@ -0,0 +1,78 @@
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
? process.cwd()
: join(process.cwd(), "apps", "web");
const repoRoot = existsSync(join(process.cwd(), "turbo.json"))
? process.cwd()
: join(appRoot, "..", "..");
const presentationRoots = [
join(repoRoot, "packages", "ui", "src"),
join(appRoot, "src", "App.tsx"),
join(appRoot, "src", "app.css"),
join(appRoot, "src", "lib", "ui-adapter"),
];
const forbiddenTerms = [
"dimensionlab",
"dimension lab",
"vaultwarden",
"forgejo",
"grafana",
"uptime kuma",
"prometheus",
"backrest",
"open webui",
"comfyui",
"adminer",
"cockpit",
"ollama",
"dimensionlab.net",
];
describe("presentation content boundary", () => {
test("keeps environment-specific content out of route and UI implementation", () => {
const source = withoutInternalPackageScope(
presentationRoots.map(readPresentationSource).join("\n").toLowerCase(),
);
expect(forbiddenTerms.filter((term) => source.includes(term))).toEqual([]);
});
test("does not keep legacy presentation component files in the React runtime", () => {
const legacyExtension = [".sve", "lte"].join("");
expect(findFiles(join(appRoot, "src"), legacyExtension)).toEqual([]);
});
});
function readPresentationSource(path: string): string {
if (!existsSync(path)) return "";
const stats = statSync(path);
if (stats.isFile()) {
if (path.endsWith(".test.ts")) return "";
if (path.includes(`${join("packages", "ui", "src", "stories")}${"/"}`)) {
return "";
}
if (!/\.(tsx|ts|js|mjs|css|json)$/.test(path)) return "";
return readFileSync(path, "utf8");
}
return readdirSync(path)
.map((entry) => readPresentationSource(join(path, entry)))
.join("\n");
}
function findFiles(path: string, extension: string): string[] {
const stats = statSync(path);
if (stats.isFile()) return path.endsWith(extension) ? [path] : [];
return readdirSync(path).flatMap((entry) => findFiles(join(path, entry), extension));
}
function withoutInternalPackageScope(source: string): string {
return source.replaceAll("@dimensionlab/ui", "@internal/ui");
}

View file

@ -0,0 +1,595 @@
import { existsSync, rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import type { DashboardDocument } from "$lib/model";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store";
import {
AgentConfigAuthorizationError,
authorizeAgentConfigRequest,
handleAgentDashboardRequest,
previewDashboardChanges,
publishDashboardChanges,
rollbackDashboardRevision,
type AgentDashboardOperation,
type JsonPatchOperation,
} from ".";
const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
afterEach(() => {
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
describe("agent dashboard configuration API", () => {
test("previews typed dashboard operations with an RFC 6902-compatible patch", () => {
const operations = exampleOperations();
const result = previewDashboardChanges(genericDashboardFixture, operations);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected preview success");
expect(result.document.serviceGroups[0]?.id).toBe("edge");
expect(result.document.serviceGroups[0]?.services[0]).toMatchObject({
id: "edge-router",
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://edge.example.test/api/status",
},
});
expect(result.document.layout.telemetry).toEqual([
"service-uptime",
"edge-latency",
"queue-depth",
]);
expect(genericDashboardFixture.serviceGroups.map((group) => group.id)).toEqual([
"core-services",
]);
expect(result.patch.length).toBeGreaterThan(0);
expect(result.patch.every((operation) => operation.path.startsWith("/"))).toBe(true);
expect(result.patch.map((operation) => operation.op)).toContain("add");
});
test("returns structured repairable errors for invalid operations", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "add_service",
groupId: "missing-group",
service: exampleService(),
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected preview failure");
expect(result.errors).toEqual([
expect.objectContaining({
code: "group_not_found",
operationIndex: 0,
path: "/serviceGroups",
}),
]);
});
test("rejects unsupported target-specific mutations", () => {
const datasourceResult = previewDashboardChanges(genericDashboardFixture, [
{
type: "connect_datasource",
target: { kind: "statusItem", stripId: "runtime", id: "status" },
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://status.example.test/api",
},
},
]);
expect(datasourceResult.ok).toBe(false);
if (datasourceResult.ok) throw new Error("expected datasource failure");
expect(datasourceResult.errors[0]).toMatchObject({
code: "unsupported_target",
operationIndex: 0,
path: "/statusStrips",
});
const thresholdResult = previewDashboardChanges(genericDashboardFixture, [
{
type: "set_status_rule",
target: { kind: "service", id: "identity" },
thresholds: { warning: 1 },
},
]);
expect(thresholdResult.ok).toBe(false);
if (thresholdResult.ok) throw new Error("expected threshold failure");
expect(thresholdResult.errors[0]).toMatchObject({
code: "unsupported_target",
operationIndex: 0,
path: "/serviceGroups",
});
});
test("removes status strip items through the shared remove operation", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "sync" },
},
]);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected remove success");
expect(result.document.statusStrips[0]?.items.map((item) => item.id)).toEqual([
"status",
]);
expect(result.patch.map((operation) => operation.op)).toContain("remove");
});
test("returns RFC 6902-applicable patches for multiple array removals", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "status" },
},
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "sync" },
},
]);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected remove success");
expect(
result.patch
.filter((operation) => operation.op === "remove")
.map((operation) => operation.path),
).toEqual(["/statusStrips/0/items/1", "/statusStrips/0/items/0"]);
expect(applyJsonPatch(genericDashboardFixture, result.patch)).toMatchObject({
statusStrips: [{ items: [] }],
});
});
test("rejects ambiguous service and status targets", () => {
const duplicateDocument = documentWithDuplicateNestedIds();
const connect = previewDashboardChanges(duplicateDocument, [
{
type: "connect_datasource",
target: { kind: "service", id: "identity" },
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://identity.example.test/health",
},
},
]);
expect(connect.ok).toBe(false);
if (connect.ok) throw new Error("expected ambiguous service failure");
expect(connect.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const remove = previewDashboardChanges(duplicateDocument, [
{
type: "remove_item",
target: { kind: "service", id: "identity" },
},
]);
expect(remove.ok).toBe(false);
if (remove.ok) throw new Error("expected ambiguous removal failure");
expect(remove.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const status = previewDashboardChanges(duplicateDocument, [
{
type: "set_status_rule",
target: { kind: "statusItem", id: "status" },
value: "Healthy",
},
]);
expect(status.ok).toBe(false);
if (status.ok) throw new Error("expected ambiguous status failure");
expect(status.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/statusStrips",
});
});
test("rejects create_dashboard when it is not the first operation", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "telemetry", id: "queue-depth" },
},
{
type: "create_dashboard",
document: genericDashboardFixture,
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected sequence failure");
expect(result.errors[0]).toMatchObject({
code: "invalid_operation_sequence",
operationIndex: 1,
path: "/1",
});
});
test("publishes valid operations as a persisted dashboard revision", async () => {
const { dbPath, store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
message: "initial dashboard",
});
const result = publishDashboardChanges(store, exampleOperations(), {
actor: "agent",
message: "add edge router",
});
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected publish success");
expect(existsSync(dbPath)).toBe(true);
expect(result.revision.operation).toBe("commit");
expect(result.revision.actor).toBe("agent");
expect(result.revision.message).toBe("add edge router");
expect(result.previousRevisionId).toBe(seed.id);
expect(result.patch.length).toBeGreaterThan(0);
expect(store.getActiveDashboard()?.document.serviceGroups[0]?.id).toBe("edge");
expect(store.listRevisions()).toHaveLength(2);
});
test("does not publish invalid operations", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const result = publishDashboardChanges(store, [
{
type: "remove_item",
target: { kind: "telemetry", id: "missing-metric" },
},
]);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected publish failure");
expect(result.errors[0]).toMatchObject({
code: "item_not_found",
operationIndex: 0,
});
expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id);
expect(store.listRevisions()).toHaveLength(1);
});
test("rolls back through the agent-safe revision path", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const publish = publishDashboardChanges(store, exampleOperations(), {
actor: "agent",
});
expect(publish.ok).toBe(true);
const rollback = rollbackDashboardRevision(store, seed.id, {
actor: "agent",
message: "restore previous dashboard",
});
expect(rollback.operation).toBe("rollback");
expect(rollback.actor).toBe("agent");
expect(rollback.sourceRevisionId).toBe(seed.id);
expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions().map((revision) => revision.operation)).toEqual([
"rollback",
"commit",
"seed",
]);
});
test("authenticates agent configuration requests with a shared token", () => {
const authorized = new Request("https://dimensionlab.test/api/agent/dashboard", {
headers: { authorization: "Bearer shared-secret" },
});
const rejected = new Request("https://dimensionlab.test/api/agent/dashboard");
expect(authorizeAgentConfigRequest(authorized, "shared-secret")).toEqual({
ok: true,
});
expect(() => authorizeAgentConfigRequest(rejected, "shared-secret")).toThrow(
AgentConfigAuthorizationError,
);
expect(() => authorizeAgentConfigRequest(authorized, "")).toThrow(
AgentConfigAuthorizationError,
);
});
test("handles preview, publish, and rollback requests through the HTTP adapter", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const preview = await handleAgentDashboardRequest(
jsonRequest({
action: "preview_changes",
operations: exampleOperations(),
}),
{ store, token: "shared-secret" },
);
expect(preview.status).toBe(200);
expect(await preview.json()).toMatchObject({
ok: true,
action: "preview_changes",
patch: expect.any(Array),
});
expect(store.listRevisions()).toHaveLength(1);
const publish = await handleAgentDashboardRequest(
jsonRequest({
action: "publish_changes",
actor: "agent",
message: "publish edge router",
operations: exampleOperations(),
}),
{ store, token: "shared-secret" },
);
expect(publish.status).toBe(200);
expect(await publish.json()).toMatchObject({
ok: true,
action: "publish_changes",
revision: { operation: "commit", actor: "agent" },
});
expect(store.listRevisions()).toHaveLength(2);
const rollback = await handleAgentDashboardRequest(
jsonRequest({
action: "rollback_revision",
actor: "agent",
revisionId: seed.id,
}),
{ store, token: "shared-secret" },
);
expect(rollback.status).toBe(200);
expect(await rollback.json()).toMatchObject({
ok: true,
action: "rollback_revision",
revision: { operation: "rollback", sourceRevisionId: seed.id },
});
});
test("returns a structured error for unknown rollback revisions", async () => {
const { store } = await createTestStore();
store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "seed",
});
const rollback = await handleAgentDashboardRequest(
jsonRequest({
action: "rollback_revision",
actor: "agent",
revisionId: "missing-revision",
}),
{ store, token: "shared-secret" },
);
expect(rollback.status).toBe(404);
expect(await rollback.json()).toMatchObject({
ok: false,
errors: [
{
code: "revision_not_found",
path: "/revisionId",
},
],
});
});
test("previews create_dashboard requests against an empty store", async () => {
const { store } = await createTestStore();
const preview = await handleAgentDashboardRequest(
jsonRequest({
action: "preview_changes",
operations: [
{
type: "create_dashboard",
document: genericDashboardFixture,
},
],
}),
{ store, token: "shared-secret" },
);
expect(preview.status).toBe(200);
expect(await preview.json()).toMatchObject({
ok: true,
action: "preview_changes",
document: {
metadata: {
title: "Operations Console",
},
},
});
expect(store.listRevisions()).toHaveLength(0);
});
});
async function createTestStore() {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-agent-config-"));
tempRoots.push(root);
const dbPath = join(root, "dashboard.sqlite");
const store = createDashboardStore({
databaseUrl: `file:${dbPath}`,
});
stores.push(store);
return { dbPath, store };
}
function exampleOperations(): AgentDashboardOperation[] {
return [
{
type: "add_section",
section: {
id: "edge",
title: "Edge",
layout: "list",
},
},
{
type: "add_service",
groupId: "edge",
service: exampleService(),
},
{
type: "add_metric_card",
card: {
id: "edge-latency",
label: "Edge Latency",
value: { kind: "latency", value: 12, precision: 0 },
severity: "ok",
detail: "p95",
datasource: {
type: "external",
adapter: "prometheus",
reference: "histogram_quantile(0.95, edge_request_duration_seconds_bucket)",
},
},
position: { afterId: "service-uptime" },
},
{
type: "connect_datasource",
target: {
kind: "service",
groupId: "edge",
id: "edge-router",
},
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://edge.example.test/api/status",
},
},
{
type: "set_status_rule",
target: { kind: "telemetry", id: "edge-latency" },
thresholds: { warning: 50, danger: 100 },
},
{
type: "arrange_item",
area: "serviceGroups",
id: "edge",
index: 0,
},
];
}
function exampleService() {
return {
id: "edge-router",
label: "Edge Router",
description: "Ingress and routing",
icon: "mdi:router-network",
severity: "ok" as const,
detail: "pending datasource",
datasource: { type: "placeholder" as const, reason: "health adapter pending" },
link: {
href: "https://edge.example.test",
label: "Open Edge Router",
external: true,
},
};
}
function documentWithDuplicateNestedIds(): DashboardDocument {
const document = structuredClone(genericDashboardFixture);
document.layout.serviceGroups.push("secondary-services");
document.serviceGroups.push({
id: "secondary-services",
title: "Secondary Services",
layout: "list",
services: [
{
...document.serviceGroups[0].services[0],
label: "Shadow Identity",
},
],
});
document.layout.statusStrips.push("secondary-runtime");
document.statusStrips.push({
id: "secondary-runtime",
items: [
{
...document.statusStrips[0].items[0],
value: "Healthy",
},
],
});
return document;
}
function applyJsonPatch<T>(value: T, patch: JsonPatchOperation[]): T {
const next = structuredClone(value);
for (const operation of patch) {
const { parent, key } = jsonPointerTarget(next, operation.path);
if (operation.op === "remove") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 1);
} else {
delete parent[key];
}
} else if (operation.op === "add") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 0, operation.value);
} else {
parent[key] = operation.value;
}
} else if (operation.op === "replace") {
if (Array.isArray(parent)) {
parent[Number(key)] = operation.value;
} else {
parent[key] = operation.value;
}
}
}
return next;
}
function jsonPointerTarget(value: unknown, path: string) {
const segments = path
.split("/")
.slice(1)
.map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
const key = segments.pop();
if (key === undefined) throw new Error(`Invalid JSON pointer: ${path}`);
let parent = value as Record<string, unknown> | unknown[];
for (const segment of segments) {
parent = Array.isArray(parent)
? (parent[Number(segment)] as Record<string, unknown> | unknown[])
: (parent[segment] as Record<string, unknown> | unknown[]);
}
return { parent, key };
}
function jsonRequest(body: unknown) {
return new Request("https://dimensionlab.test/api/agent/dashboard", {
body: JSON.stringify(body),
headers: {
authorization: "Bearer shared-secret",
"content-type": "application/json",
},
method: "POST",
});
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,150 @@
import { rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { createDashboardStore, type DashboardStore } from "./db/dashboard-store";
import { loadDashboardRuntime } from "./dashboard";
const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
afterEach(() => {
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
describe("dashboard runtime loader", () => {
test("seeds and loads the active dashboard from sqlite", async () => {
const store = await createTestStore();
const runtime = loadDashboardRuntime(store, { seedIfEmpty: true });
expect(runtime.state).toBe("ready");
if (runtime.state !== "ready") throw new Error("expected ready dashboard");
expect(runtime.document.metadata.title).toBe(dimensionLabDashboardFixture.metadata.title);
expect(runtime.document.metadata.subtitle).toBe(dimensionLabDashboardFixture.metadata.subtitle);
expect(runtime.schemaVersion).toBe(dimensionLabDashboardFixture.schemaVersion);
expect(runtime.currentRevisionId).toBe(store.getActiveDashboard()?.currentRevisionId);
expect(store.listRevisions()).toHaveLength(1);
});
test("loads an existing active dashboard without reseeding", async () => {
const store = await createTestStore();
const seed = store.commitDashboard(genericDashboardFixture, {
actor: "test",
message: "existing dashboard",
});
const runtime = loadDashboardRuntime(store);
expect(runtime.state).toBe("ready");
if (runtime.state !== "ready") throw new Error("expected ready dashboard");
expect(runtime.currentRevisionId).toBe(seed.id);
expect(runtime.document.metadata.title).toBe(genericDashboardFixture.metadata.title);
expect(store.listRevisions()).toHaveLength(1);
});
test("refreshes an existing initial seed when the bundled seed changes", async () => {
const store = await createTestStore();
const oldSeed = olderDimensionLabSeed();
store.seedDashboardIfEmpty(oldSeed, {
actor: "initial-seed",
message: "load initial dashboard document",
});
const runtime = loadDashboardRuntime(store, {
refreshSeedDocument: true,
seedDocument: dimensionLabDashboardFixture,
seedIfEmpty: true,
});
expect(runtime.state).toBe("ready");
if (runtime.state !== "ready") throw new Error("expected ready dashboard");
expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).toContain(
"auto-refresh",
);
expect(runtime.document.serviceGroups.flatMap((group) => group.services).map((service) => service.id)).toContain(
"prompt-registry",
);
expect(store.listRevisions()).toHaveLength(2);
expect(store.getActiveDashboard()?.revision.actor).toBe("initial-seed");
});
test("does not refresh a dashboard after a user-authored revision", async () => {
const store = await createTestStore();
const oldSeed = olderDimensionLabSeed();
store.seedDashboardIfEmpty(oldSeed, {
actor: "initial-seed",
message: "load initial dashboard document",
});
store.commitDashboard(
{
...oldSeed,
metadata: {
...oldSeed.metadata,
title: "Custom Dashboard",
},
},
{
actor: "agent",
message: "customize dashboard",
},
);
const runtime = loadDashboardRuntime(store, {
refreshSeedDocument: true,
seedDocument: dimensionLabDashboardFixture,
seedIfEmpty: true,
});
expect(runtime.state).toBe("ready");
if (runtime.state !== "ready") throw new Error("expected ready dashboard");
expect(runtime.document.metadata.title).toBe("Custom Dashboard");
expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).not.toContain(
"auto-refresh",
);
expect(store.listRevisions()).toHaveLength(2);
});
test("returns empty state when no dashboard is active and seeding is disabled", async () => {
const store = await createTestStore();
const runtime = loadDashboardRuntime(store);
expect(runtime.state).toBe("empty");
if (runtime.state !== "empty") throw new Error("expected empty dashboard");
expect(runtime.title).toBe("No Dashboard Model");
expect(store.listRevisions()).toHaveLength(0);
});
});
async function createTestStore() {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-runtime-"));
tempRoots.push(root);
const store = createDashboardStore({
databaseUrl: `file:${join(root, "dashboard.sqlite")}`,
});
stores.push(store);
return store;
}
function olderDimensionLabSeed() {
const document = structuredClone(dimensionLabDashboardFixture);
document.statusStrips = document.statusStrips.map((strip) => ({
...strip,
items: strip.items.filter((item) => item.id !== "auto-refresh"),
}));
document.serviceGroups = document.serviceGroups.map((group) =>
group.id === "ai-automation"
? {
...group,
services: group.services.filter((service) => service.id !== "prompt-registry"),
}
: group,
);
return document;
}

View file

@ -0,0 +1,147 @@
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
import type { DashboardDocument } from "$lib/model";
import {
createDashboardStore,
DashboardPersistenceValidationError,
type DashboardStore,
} from "$lib/server/db/dashboard-store";
import { UnsupportedDashboardModelVersionError } from "$lib/server/db/model-migrations";
export type DashboardRuntimeState =
| DashboardRuntimeEmpty
| DashboardRuntimeInvalid
| DashboardRuntimeLoading
| DashboardRuntimeReady;
export interface DashboardRuntimeReady {
state: "ready";
document: DashboardDocument;
schemaVersion: string;
currentRevisionId: string;
}
export interface DashboardRuntimeEmpty {
state: "empty";
title: string;
subtitle: string;
message: string;
}
export interface DashboardRuntimeLoading {
state: "loading";
title: string;
subtitle: string;
message: string;
}
export interface DashboardRuntimeInvalid {
state: "invalid";
title: string;
subtitle: string;
message: string;
errors: string[];
}
export interface DashboardRuntimeOptions {
refreshSeedDocument?: boolean;
seedIfEmpty?: boolean;
seedDocument?: DashboardDocument;
}
export function loadDashboardRuntime(
store?: DashboardStore,
options: DashboardRuntimeOptions = {},
): DashboardRuntimeState {
const dashboardStore = store || createDashboardStore();
try {
const seedDocument = options.seedDocument || dimensionLabDashboardFixture;
const active = dashboardStore.getActiveDashboard();
if (active) {
if (
options.refreshSeedDocument &&
shouldRefreshSeedDashboard(active, seedDocument)
) {
const refreshed = dashboardStore.commitDashboard(seedDocument, {
actor: "initial-seed",
message: "refresh bundled dashboard document",
});
return readyRuntimeState(refreshed.document, refreshed.id);
}
return readyRuntimeState(active.document, active.currentRevisionId);
}
if (!options.seedIfEmpty) {
return {
state: "empty",
title: "No Dashboard Model",
subtitle: "No active document",
message: "No validated dashboard document is active yet.",
};
}
const seeded = dashboardStore.seedDashboardIfEmpty(
seedDocument,
{
actor: "initial-seed",
message: "load initial dashboard document",
},
);
return readyRuntimeState(seeded.document, seeded.id);
} catch (error) {
if (error instanceof DashboardPersistenceValidationError) {
return invalidRuntimeState(error.failure.errors);
}
if (error instanceof UnsupportedDashboardModelVersionError) {
return invalidRuntimeState([error.message]);
}
throw error;
} finally {
if (!store) dashboardStore.close();
}
}
function readyRuntimeState(
document: DashboardDocument,
currentRevisionId: string,
): DashboardRuntimeReady {
return {
state: "ready",
document,
schemaVersion: document.schemaVersion,
currentRevisionId,
};
}
function invalidRuntimeState(errors: string[]): DashboardRuntimeInvalid {
return {
state: "invalid",
title: "Invalid Dashboard Model",
subtitle: "Validation failed",
message: "The active dashboard document could not be validated.",
errors,
};
}
function shouldRefreshSeedDashboard(
active: { document: DashboardDocument; revision: { actor: string } },
seedDocument: DashboardDocument,
): boolean {
if (active.revision.actor !== "initial-seed") return false;
if (!isBundledDimensionLabSeed(active.document, seedDocument)) return false;
return JSON.stringify(active.document) !== JSON.stringify(seedDocument);
}
function isBundledDimensionLabSeed(
document: DashboardDocument,
seedDocument: DashboardDocument,
): boolean {
return (
document.metadata.title === seedDocument.metadata.title &&
document.metadata.description === seedDocument.metadata.description
);
}

View file

@ -0,0 +1,240 @@
import { describe, expect, test, vi } from "vitest";
import {
DASHBOARD_SCHEMA_VERSION,
type DashboardDocument,
} from "$lib/model";
import { resolveDashboardDatasources } from ".";
describe("dashboard datasource resolution", () => {
test("hydrates telemetry, service health, weather, and summary data from live adapters", 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, "40"],
[1771430120, "30"],
],
},
{
metric: { host: "linux-gpu" },
values: [
[1771430000, "12"],
[1771430060, "24"],
[1771430120, "36"],
],
},
],
},
});
}
if (url.startsWith("https://prometheus.example/api/v1/query")) {
const query = new URL(url).searchParams.get("query") || "";
if (query.includes("node_boot_time_seconds")) {
return prometheusVector("90061");
}
if (query.includes("node_load15")) return prometheusVector("0.40");
if (query.includes("node_load5")) return prometheusVector("0.46");
if (query.includes("node_load1")) return prometheusVector("0.34");
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra", mountpoint: "/home" },
value: [1771430400, "88"],
},
],
},
});
}
if (url.startsWith("https://api.open-meteo.com/v1/forecast")) {
return jsonResponse({
current: {
apparent_temperature: 20.9,
temperature_2m: 21.4,
weather_code: 0,
wind_speed_10m: 12,
},
});
}
if (url === "https://service.example/health") {
return jsonResponse({
status: "UP",
ping: 42,
});
}
throw new Error(`Unhandled test request: ${url}`);
});
const resolved = await resolveDashboardDatasources(testDashboard(), {
fetch,
prometheusBaseUrl: "https://prometheus.example",
});
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" }),
);
const telemetry = resolved.telemetry[0];
expect(telemetry.value).toEqual({ kind: "percent", value: 88 });
expect(telemetry.severity).toBe("warning");
expect(telemetry.detail).toBe("linux-infra /home");
expect(telemetry.sparkline).toEqual([12, 40, 36]);
const service = resolved.serviceGroups[0].services[0];
expect(service.severity).toBe("ok");
expect(service.detail).toBe("42 ms");
const weather = resolved.modules?.find((module) => module.id === "weather-amsterdam");
expect(weather?.value).toBe("21.4 C");
expect(weather?.detail).toBe("Clear - feels 20.9 C - wind 12 km/h");
expect(weather?.severity).toBe("ok");
const summary = resolved.modules?.find((module) => module.id === "runtime-health-summary");
expect(summary?.value).toBe("all systems operational");
expect(summary?.detail).toBe("1 service ok");
expect(summary?.severity).toBe("ok");
expect(resolved.statusStrips[0].items).toEqual([
{ id: "system-status", label: "System Status", value: "All systems operational", severity: "ok" },
{ id: "last-sync", label: "Last Sync", value: "just now", severity: "ok" },
{ id: "uptime", label: "Uptime", value: "1d 1h 1m", severity: "ok" },
{ id: "load-avg", label: "Load Avg", value: "0.34 0.46 0.40", severity: "neutral" },
{ id: "auto-refresh", label: "Auto Refresh", value: "15s", severity: "neutral" },
]);
expect(resolved).not.toBe(testDocument);
expect(testDocument.telemetry[0].value.value).toBe(1);
});
});
const testDocument = testDashboard();
function testDashboard(): DashboardDocument {
return {
schemaVersion: DASHBOARD_SCHEMA_VERSION,
metadata: {
title: "System Overview",
refreshIntervalSeconds: 15,
},
layout: {
telemetry: ["cpu"],
serviceGroups: ["services"],
statusStrips: ["footer"],
modules: ["weather-amsterdam", "runtime-health-summary"],
},
telemetry: [
{
id: "cpu",
label: "CPU",
value: { kind: "percent", value: 1 },
detail: "fallback",
severity: "stale",
thresholds: { warning: 70, danger: 90 },
datasource: {
type: "external",
adapter: "prometheus",
reference: "fixture_cpu_query",
},
sparkline: [1],
},
],
serviceGroups: [
{
id: "services",
title: "Services",
services: [
{
id: "api",
label: "API",
description: "Example API",
severity: "stale",
detail: "fallback",
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://service.example/health",
},
},
],
},
],
statusStrips: [
{
id: "footer",
items: [
{ id: "system-status", label: "System Status", value: "fallback", severity: "ok" },
{ id: "last-sync", label: "Last Sync", value: "fallback", severity: "stale" },
{ id: "uptime", label: "Uptime", value: "fallback", severity: "ok" },
{ id: "load-avg", label: "Load Avg", value: "fallback", severity: "neutral" },
{ id: "auto-refresh", label: "Auto Refresh", value: "fallback", severity: "neutral" },
],
},
],
modules: [
{
id: "weather-amsterdam",
kind: "weather",
title: "Amsterdam",
value: "fallback",
detail: "fallback",
severity: "stale",
datasource: {
type: "external",
adapter: "weather",
reference: "open-meteo:latitude=52.3676&longitude=4.9041",
},
},
{
id: "runtime-health-summary",
kind: "summary",
title: "Runtime Health",
value: "fallback",
detail: "fallback",
severity: "stale",
datasource: {
type: "placeholder",
reason: "summary pending live health aggregation",
},
},
],
};
}
function jsonResponse(payload: unknown): Response {
return new Response(JSON.stringify(payload), {
headers: { "content-type": "application/json" },
});
}
function prometheusVector(value: string): Response {
return jsonResponse({
status: "success",
data: {
result: [
{
metric: { host: "linux-infra" },
value: [1771430400, value],
},
],
},
});
}

View file

@ -0,0 +1,675 @@
import type {
DashboardDocument,
DashboardModule,
MetricValue,
ServiceEntry,
ServiceGroup,
Severity,
StatusItem,
StatusStrip,
TelemetryCard,
} from "$lib/model";
export interface DatasourceResolutionOptions {
fetch?: DatasourceFetch;
prometheusBaseUrl?: string;
prometheusRangeSeconds?: number;
prometheusStepSeconds?: number;
requestTimeoutMs?: number;
}
export async function resolveDashboardDatasources(
document: DashboardDocument,
options: DatasourceResolutionOptions = {},
): Promise<DashboardDocument> {
const context = datasourceContext(options);
const telemetry = await Promise.all(
document.telemetry.map((card) => resolveTelemetryCard(card, context)),
);
const serviceGroups = await Promise.all(
document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
);
const modules = await resolveModules(document.modules || [], serviceGroups, context);
const statusStrips = await resolveStatusStrips(
document.statusStrips,
document.metadata.refreshIntervalSeconds,
serviceGroups,
context,
);
return {
...structuredClone(document),
telemetry,
serviceGroups,
modules,
statusStrips,
};
}
interface DatasourceContext {
fetch: DatasourceFetch;
prometheusBaseUrl: string;
prometheusRangeSeconds: number;
prometheusStepSeconds: number;
requestTimeoutMs: number;
}
type DatasourceFetch = (input: string, init?: RequestInit) => Promise<Response>;
interface PrometheusVectorResult {
metric?: Record<string, string>;
value?: [number, string];
}
interface PrometheusMatrixResult {
metric?: Record<string, string>;
values?: Array<[number, string]>;
}
function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext {
return {
fetch: options.fetch || globalThis.fetch,
prometheusBaseUrl:
options.prometheusBaseUrl ||
process.env.PROMETHEUS_BASE_URL ||
"https://prometheus.dimensionlab.net",
prometheusRangeSeconds: options.prometheusRangeSeconds || 60 * 60,
prometheusStepSeconds: options.prometheusStepSeconds || 120,
requestTimeoutMs: options.requestTimeoutMs || 2_500,
};
}
async function resolveTelemetryCard(
card: TelemetryCard,
context: DatasourceContext,
): Promise<TelemetryCard> {
if (card.datasource?.type !== "external" || card.datasource.adapter !== "prometheus") {
return structuredClone(card);
}
try {
const [instant, range] = await Promise.all([
prometheusQuery(card.datasource.reference, context),
prometheusRangeQuery(card.datasource.reference, context),
]);
const current = pickMaxVectorResult(instant);
const currentValue = Number(current?.value?.[1]);
const sparkline = matrixPoints(range);
if (!Number.isFinite(currentValue)) return markStale(card, "no telemetry data");
return {
...structuredClone(card),
value: metricValueWithLiveNumber(card.value, currentValue),
severity: severityForValue(currentValue, card.thresholds),
detail: prometheusMetricDetail(current?.metric || {}, card.detail),
sparkline: sparkline.length ? sparkline : card.sparkline,
};
} catch {
return markStale(card, card.detail || "telemetry unavailable");
}
}
async function resolveServiceGroup(
group: ServiceGroup,
context: DatasourceContext,
): Promise<ServiceGroup> {
return {
...structuredClone(group),
services: await Promise.all(
group.services.map((service) => resolveService(service, context)),
),
};
}
async function resolveService(
service: ServiceEntry,
context: DatasourceContext,
): Promise<ServiceEntry> {
const datasource = service.datasource;
if (datasource?.type !== "external") return structuredClone(service);
if (datasource.adapter === "http-status") {
return resolveHttpStatusService(service, datasource.reference, context);
}
if (datasource.adapter === "prometheus") {
return resolvePrometheusService(service, datasource.reference, context);
}
return structuredClone(service);
}
async function resolveHttpStatusService(
service: ServiceEntry,
reference: string,
context: DatasourceContext,
): Promise<ServiceEntry> {
const request = parseHttpStatusReference(reference);
if (!request) return structuredClone(service);
try {
const startedAt = Date.now();
const response = await fetchWithTimeout(
context.fetch,
request.url,
{
cache: "no-store",
method: request.method,
},
context.requestTimeoutMs,
);
const elapsedMs = Math.max(0, Math.round(Date.now() - startedAt));
const badge = await uptimeBadge(response);
if (badge) {
const ping = badge.ping;
const status = badge.status || "UNKNOWN";
return {
...structuredClone(service),
severity: uptimeBadgeSeverity(status),
detail: Number.isFinite(ping)
? `${Math.round(ping as number)} ms`
: status.toLowerCase(),
};
}
return {
...structuredClone(service),
severity: response.ok ? "ok" : response.status >= 500 ? "danger" : "warning",
detail: response.ok ? `${elapsedMs} ms` : `HTTP ${response.status}`,
};
} catch {
return {
...structuredClone(service),
severity: "unavailable",
detail: "unavailable",
};
}
}
async function resolvePrometheusService(
service: ServiceEntry,
reference: string,
context: DatasourceContext,
): Promise<ServiceEntry> {
try {
const result = pickMaxVectorResult(await prometheusQuery(reference, context));
const value = Number(result?.value?.[1]);
const ok = Number.isFinite(value) && value > 0;
return {
...structuredClone(service),
severity: ok ? "ok" : "unavailable",
detail: ok ? "up" : "down",
};
} catch {
return {
...structuredClone(service),
severity: "unavailable",
detail: "unavailable",
};
}
}
async function resolveModules(
modules: DashboardModule[],
serviceGroups: ServiceGroup[],
context: DatasourceContext,
): Promise<DashboardModule[]> {
const resolved = await Promise.all(
modules.map((module) => resolveModule(module, context)),
);
return resolved.map((module) =>
module.id === "runtime-health-summary"
? runtimeHealthSummary(module, serviceGroups)
: module,
);
}
async function resolveModule(
module: DashboardModule,
context: DatasourceContext,
): Promise<DashboardModule> {
if (module.datasource?.type !== "external" || module.datasource.adapter !== "weather") {
return structuredClone(module);
}
try {
const weather = await requestJson<OpenMeteoResponse>(
context.fetch,
openMeteoUrl(module.datasource.reference),
context.requestTimeoutMs,
);
const current = weather.current;
const temperature = Number(current?.temperature_2m);
if (!Number.isFinite(temperature)) {
return {
...structuredClone(module),
severity: "stale",
detail: "weather unavailable",
};
}
const apparent = Number(current?.apparent_temperature);
const wind = Number(current?.wind_speed_10m);
const condition = weatherCondition(Number(current?.weather_code));
return {
...structuredClone(module),
value: `${temperature.toFixed(1)} C`,
detail: [
condition,
Number.isFinite(apparent) ? `feels ${apparent.toFixed(1)} C` : "",
Number.isFinite(wind) ? `wind ${Math.round(wind)} km/h` : "",
].filter(Boolean).join(" - "),
severity: "ok",
};
} catch {
return {
...structuredClone(module),
severity: "stale",
detail: "weather unavailable",
};
}
}
function runtimeHealthSummary(
module: DashboardModule,
serviceGroups: ServiceGroup[],
): DashboardModule {
const services = serviceGroups.flatMap((group) => group.services);
const down = services.filter((service) =>
service.severity === "danger" || service.severity === "unavailable"
).length;
const warning = services.filter((service) => service.severity === "warning").length;
const ok = services.filter((service) => service.severity === "ok").length;
if (down > 0) {
return {
...structuredClone(module),
value: `${down} service${down === 1 ? "" : "s"} down`,
detail: `${warning} warning${warning === 1 ? "" : "s"} - ${ok} service${ok === 1 ? "" : "s"} ok`,
severity: "danger",
};
}
if (warning > 0) {
return {
...structuredClone(module),
value: `${warning} service${warning === 1 ? "" : "s"} warning`,
detail: `${ok} service${ok === 1 ? "" : "s"} ok`,
severity: "warning",
};
}
return {
...structuredClone(module),
value: "all systems operational",
detail: `${ok} service${ok === 1 ? "" : "s"} ok`,
severity: "ok",
};
}
async function resolveStatusStrips(
strips: StatusStrip[],
refreshIntervalSeconds: number | undefined,
serviceGroups: ServiceGroup[],
context: DatasourceContext,
): Promise<StatusStrip[]> {
const [uptime, loadAverage] = await Promise.all([
prometheusScalar(
'time() - node_boot_time_seconds{job="node",host="linux-infra"}',
context,
).catch(() => null),
prometheusLoadAverage(context).catch(() => null),
]);
const health = serviceHealthSummary(serviceGroups);
return strips.map((strip) => ({
...structuredClone(strip),
items: strip.items.map((item) =>
resolveStatusItem(item, {
health,
loadAverage,
refreshIntervalSeconds,
uptime,
}),
),
}));
}
function resolveStatusItem(
item: StatusItem,
values: {
health: { severity: Severity; value: string };
loadAverage: string | null;
refreshIntervalSeconds?: number;
uptime: number | null;
},
): StatusItem {
if (item.id === "system-status") {
return {
...structuredClone(item),
value: values.health.value,
severity: values.health.severity,
};
}
if (item.id === "last-sync") {
return {
...structuredClone(item),
value: "just now",
severity: "ok",
};
}
if (item.id === "uptime" && values.uptime !== null) {
return {
...structuredClone(item),
value: formatDuration(values.uptime),
severity: "ok",
};
}
if (item.id === "load-avg" && values.loadAverage) {
return {
...structuredClone(item),
value: values.loadAverage,
severity: "neutral",
};
}
if (item.id === "auto-refresh" && values.refreshIntervalSeconds) {
return {
...structuredClone(item),
value: `${values.refreshIntervalSeconds}s`,
severity: "neutral",
};
}
return structuredClone(item);
}
function serviceHealthSummary(serviceGroups: ServiceGroup[]): {
severity: Severity;
value: string;
} {
const services = serviceGroups.flatMap((group) => group.services);
const down = services.filter((service) =>
service.severity === "danger" || service.severity === "unavailable"
).length;
const warning = services.filter((service) => service.severity === "warning").length;
if (down > 0) {
return {
severity: "danger",
value: `${down} service${down === 1 ? "" : "s"} down`,
};
}
if (warning > 0) {
return {
severity: "warning",
value: `${warning} service${warning === 1 ? "" : "s"} warning`,
};
}
return {
severity: "ok",
value: "All systems operational",
};
}
async function prometheusQuery(
query: string,
context: DatasourceContext,
): Promise<PrometheusVectorResult[]> {
const url = prometheusApiUrl(context.prometheusBaseUrl, "/api/v1/query", {
query,
});
const payload = await requestJson<PrometheusResponse<PrometheusVectorResult>>(
context.fetch,
url,
context.requestTimeoutMs,
);
return payload.status === "success" ? payload.data.result || [] : [];
}
async function prometheusRangeQuery(
query: string,
context: DatasourceContext,
): Promise<PrometheusMatrixResult[]> {
const end = Math.floor(Date.now() / 1000);
const start = end - context.prometheusRangeSeconds;
const url = prometheusApiUrl(context.prometheusBaseUrl, "/api/v1/query_range", {
query,
start: String(start),
end: String(end),
step: String(context.prometheusStepSeconds),
});
const payload = await requestJson<PrometheusResponse<PrometheusMatrixResult>>(
context.fetch,
url,
context.requestTimeoutMs,
);
return payload.status === "success" ? payload.data.result || [] : [];
}
async function prometheusScalar(
query: string,
context: DatasourceContext,
): Promise<number | null> {
const result = pickMaxVectorResult(await prometheusQuery(query, context));
const value = Number(result?.value?.[1]);
return Number.isFinite(value) ? value : null;
}
async function prometheusLoadAverage(context: DatasourceContext): Promise<string | null> {
const [one, five, fifteen] = await Promise.all([
prometheusScalar('node_load1{job="node",host="linux-infra"}', context),
prometheusScalar('node_load5{job="node",host="linux-infra"}', context),
prometheusScalar('node_load15{job="node",host="linux-infra"}', context),
]);
if (one === null || five === null || fifteen === null) return null;
return [one, five, fifteen].map((value) => value.toFixed(2)).join(" ");
}
async function requestJson<T>(
fetch: DatasourceFetch,
url: string,
timeoutMs: number,
): Promise<T> {
const response = await fetchWithTimeout(
fetch,
url,
{ cache: "no-store" },
timeoutMs,
);
if (!response.ok) throw new Error(`request failed: ${response.status}`);
return response.json() as Promise<T>;
}
async function fetchWithTimeout(
fetch: DatasourceFetch,
url: string,
init: RequestInit,
timeoutMs: number,
): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, {
...init,
signal: controller.signal,
});
} finally {
clearTimeout(timeout);
}
}
function prometheusApiUrl(
baseUrl: string,
pathname: string,
params: Record<string, string>,
): string {
const url = new URL(pathname, ensureTrailingSlash(baseUrl));
Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value));
return url.toString();
}
function ensureTrailingSlash(value: string): string {
return value.endsWith("/") ? value : `${value}/`;
}
function pickMaxVectorResult(
results: PrometheusVectorResult[],
): PrometheusVectorResult | null {
return results.reduce<PrometheusVectorResult | null>((winner, item) => {
if (!winner) return item;
return Number(item.value?.[1]) > Number(winner.value?.[1]) ? item : winner;
}, null);
}
function matrixPoints(results: PrometheusMatrixResult[]): number[] {
const byTimestamp = new Map<number, number>();
for (const series of results) {
for (const [timestamp, value] of series.values || []) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) continue;
const previous = byTimestamp.get(timestamp);
if (previous === undefined || numeric > previous) {
byTimestamp.set(timestamp, numeric);
}
}
}
return [...byTimestamp.entries()]
.sort(([left], [right]) => left - right)
.map(([, value]) => value);
}
function metricValueWithLiveNumber(value: MetricValue, nextValue: number): MetricValue {
if (value.kind === "text") return value;
return {
...value,
value: value.kind === "percent" ? clamp(nextValue, 0, 100) : Math.max(0, nextValue),
};
}
function severityForValue(
value: number,
thresholds: TelemetryCard["thresholds"],
): Severity {
if (thresholds?.danger !== undefined && value >= thresholds.danger) return "danger";
if (thresholds?.warning !== undefined && value >= thresholds.warning) return "warning";
return "ok";
}
function prometheusMetricDetail(
metric: Record<string, string>,
fallback = "",
): string {
const host = metric.host || metric.instance || metric.job;
const detail = metric.mountpoint || metric.name || metric.container || metric.id;
const parts = [host, detail].filter(Boolean);
if (parts.length) return parts.join(" ");
return fallback.replace(/^fallback\s*-\s*/i, "") || "telemetry";
}
function markStale(card: TelemetryCard, detail: string): TelemetryCard {
return {
...structuredClone(card),
severity: "stale",
detail,
};
}
function parseHttpStatusReference(reference: string): { method: string; url: string } | null {
const match = /^(GET|HEAD|POST)\s+(.+)$/i.exec(reference.trim());
if (!match) return null;
return { method: match[1].toUpperCase(), url: match[2] };
}
interface PrometheusResponse<T> {
status: string;
data: {
result?: T[];
};
}
interface OpenMeteoResponse {
current?: {
apparent_temperature?: number;
temperature_2m?: number;
weather_code?: number;
wind_speed_10m?: number;
};
}
interface UptimeBadgeResponse {
status?: string;
ping?: number;
}
async function uptimeBadge(response: Response): Promise<UptimeBadgeResponse | null> {
const contentType = response.headers.get("content-type") || "";
if (!contentType.includes("json")) return null;
try {
const payload = await response.clone().json() as UptimeBadgeResponse;
return typeof payload.status === "string" ? payload : null;
} catch {
return null;
}
}
function uptimeBadgeSeverity(status = ""): Severity {
const normalized = status.toUpperCase();
if (normalized === "UP") return "ok";
if (normalized === "PENDING" || normalized === "MAINTENANCE") return "warning";
return "unavailable";
}
function openMeteoUrl(reference: string): string {
const params = new URLSearchParams();
const serialized = reference.startsWith("open-meteo:")
? reference.slice("open-meteo:".length)
: reference;
new URLSearchParams(serialized).forEach((value, key) => {
params.set(key, value);
});
params.set(
"current",
"temperature_2m,apparent_temperature,weather_code,wind_speed_10m",
);
params.set("timezone", "Europe/Amsterdam");
return `https://api.open-meteo.com/v1/forecast?${params.toString()}`;
}
function weatherCondition(code: number): string {
if (code === 0) return "Clear";
if ([1, 2].includes(code)) return "Partly cloudy";
if (code === 3) return "Cloudy";
if ([45, 48].includes(code)) return "Fog";
if (code >= 51 && code <= 67) return "Rain";
if (code >= 71 && code <= 77) return "Snow";
if (code >= 80 && code <= 82) return "Showers";
if (code >= 95) return "Thunderstorm";
return "Mixed";
}
function clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
function formatDuration(totalSeconds: number): string {
const seconds = Math.max(0, Math.floor(totalSeconds));
const days = Math.floor(seconds / 86_400);
const hours = Math.floor((seconds % 86_400) / 3_600);
const minutes = Math.floor((seconds % 3_600) / 60);
return `${days}d ${hours}h ${minutes}m`;
}

View file

@ -0,0 +1,47 @@
import { mkdirSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { Database } from "bun:sqlite";
import { drizzle, type BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
import { dashboardDbSchema } from "./schema";
import { applyDashboardMigrations } from "./migrations";
export const DEFAULT_DATABASE_URL = "file:./data/dimensionlab.sqlite";
export type DashboardDatabase = BunSQLiteDatabase<typeof dashboardDbSchema> & {
$client: Database;
};
export interface DashboardDatabaseConnection {
db: DashboardDatabase;
sqlite: Database;
filename: string;
close(): void;
}
export function openDashboardDatabase(
databaseUrl = process.env.DATABASE_URL || DEFAULT_DATABASE_URL,
): DashboardDatabaseConnection {
const filename = resolveSqliteFilename(databaseUrl);
mkdirSync(dirname(filename), { recursive: true });
const sqlite = new Database(filename, { create: true, readwrite: true });
const db = drizzle(sqlite, { schema: dashboardDbSchema }) as DashboardDatabase;
applyDashboardMigrations(db);
return {
db,
sqlite,
filename,
close() {
sqlite.close();
},
};
}
export function resolveSqliteFilename(databaseUrl: string): string {
if (!databaseUrl.startsWith("file:")) {
throw new Error(`Only file: SQLite DATABASE_URL values are supported: ${databaseUrl}`);
}
return resolve(databaseUrl.slice("file:".length));
}

View file

@ -0,0 +1,205 @@
import { existsSync, rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import type { DashboardDocument } from "$lib/model";
import {
DashboardPersistenceValidationError,
createDashboardStore,
type DashboardStore,
} from "./dashboard-store";
const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
const originalCwd = process.cwd();
afterEach(() => {
vi.useRealTimers();
process.chdir(originalCwd);
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
describe("dashboard persistence store", () => {
test("seeds the first active dashboard and creates the sqlite file", async () => {
const { dbPath, store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
message: "initial fixture",
});
expect(existsSync(dbPath)).toBe(true);
expect(seed.operation).toBe("seed");
expect(seed.actor).toBe("test-seed");
expect(seed.schemaVersion).toBe(genericDashboardFixture.schemaVersion);
expect(seed.document.metadata.title).toBe("Operations Console");
const active = store.getActiveDashboard();
expect(active?.currentRevisionId).toBe(seed.id);
expect(active?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions()).toHaveLength(1);
});
test("commits validated updates and records revision metadata", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const updated = cloneDashboard({
...genericDashboardFixture,
metadata: {
...genericDashboardFixture.metadata,
title: "Operations Console Updated",
},
});
const revision = store.commitDashboard(updated, {
actor: "agent",
message: "rename dashboard",
});
expect(revision.operation).toBe("commit");
expect(revision.actor).toBe("agent");
expect(revision.message).toBe("rename dashboard");
expect(revision.document.metadata.title).toBe("Operations Console Updated");
expect(store.getActiveDashboard()?.currentRevisionId).toBe(revision.id);
expect(store.getRevision(seed.id)?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions().map((item) => item.id)).toEqual([
revision.id,
seed.id,
]);
});
test("rejects invalid writes without changing the active revision", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const invalid = cloneDashboard({
...genericDashboardFixture,
layout: {
...genericDashboardFixture.layout,
telemetry: ["missing-card"],
},
});
expect(() =>
store.commitDashboard(invalid, {
actor: "agent",
message: "invalid update",
}),
).toThrow(DashboardPersistenceValidationError);
const active = store.getActiveDashboard();
expect(active?.currentRevisionId).toBe(seed.id);
expect(active?.document.layout.telemetry).toEqual(genericDashboardFixture.layout.telemetry);
expect(store.listRevisions()).toHaveLength(1);
});
test("loads specific revisions and rolls back to a prior valid document", async () => {
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const updated = cloneDashboard({
...genericDashboardFixture,
metadata: {
...genericDashboardFixture.metadata,
title: "Changed Dashboard",
},
});
const change = store.commitDashboard(updated, {
actor: "agent",
message: "change title",
});
const rollback = store.rollbackToRevision(seed.id, {
actor: "operator",
message: "restore seed",
});
expect(store.getRevision(change.id)?.document.metadata.title).toBe("Changed Dashboard");
expect(rollback.operation).toBe("rollback");
expect(rollback.sourceRevisionId).toBe(seed.id);
expect(rollback.document.metadata.title).toBe("Operations Console");
expect(store.getActiveDashboard()?.currentRevisionId).toBe(rollback.id);
expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console");
expect(store.listRevisions().map((item) => item.operation)).toEqual([
"rollback",
"commit",
"seed",
]);
});
test("assigns monotonic revision timestamps for stable history ordering", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-18T12:00:00.000Z"));
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const updated = cloneDashboard({
...genericDashboardFixture,
metadata: {
...genericDashboardFixture.metadata,
title: "Changed Dashboard",
},
});
const commit = store.commitDashboard(updated, { actor: "agent" });
const rollback = store.rollbackToRevision(seed.id, { actor: "operator" });
expect([
seed.createdAt.toISOString(),
commit.createdAt.toISOString(),
rollback.createdAt.toISOString(),
]).toEqual([
"2026-06-18T12:00:00.000Z",
"2026-06-18T12:00:00.001Z",
"2026-06-18T12:00:00.002Z",
]);
expect(store.listRevisions().map((item) => item.id)).toEqual([
rollback.id,
commit.id,
seed.id,
]);
});
test("applies migrations when launched outside the repository root", async () => {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-cwd-"));
tempRoots.push(root);
process.chdir(root);
vi.resetModules();
const { createDashboardStore: createStore } = await import("./dashboard-store");
const store = createStore({
databaseUrl: `file:${join(root, "dashboard.sqlite")}`,
});
stores.push(store);
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
expect(seed.document.metadata.title).toBe("Operations Console");
expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id);
});
});
async function createTestStore() {
const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-store-"));
tempRoots.push(root);
const dbPath = join(root, "nested", "dashboard.sqlite");
const store = createDashboardStore({
databaseUrl: `file:${dbPath}`,
});
stores.push(store);
return { dbPath, store };
}
function cloneDashboard(document: DashboardDocument): DashboardDocument {
return structuredClone(document);
}

View file

@ -0,0 +1,261 @@
import { randomUUID } from "node:crypto";
import { desc, eq } from "drizzle-orm";
import {
type DashboardDocument,
type DashboardValidationFailure,
} from "$lib/model";
import {
type DashboardDatabaseConnection,
openDashboardDatabase,
} from "./connection";
import {
dashboardDocuments,
dashboardRevisions,
type DashboardRevisionOperation,
} from "./schema";
import { migrateDashboardDocumentForPersistence } from "./model-migrations";
const DEFAULT_DASHBOARD_ID = "primary";
const DEFAULT_ACTOR = "system";
export interface DashboardRevision {
id: string;
dashboardId: string;
schemaVersion: string;
document: DashboardDocument;
actor: string;
message: string | null;
operation: DashboardRevisionOperation;
sourceRevisionId: string | null;
createdAt: Date;
}
export interface ActiveDashboard {
dashboardId: string;
currentRevisionId: string;
document: DashboardDocument;
revision: DashboardRevision;
createdAt: Date;
updatedAt: Date;
}
export interface DashboardWriteMetadata {
actor?: string;
message?: string;
}
export interface DashboardStoreOptions {
databaseUrl?: string;
dashboardId?: string;
}
export interface DashboardStore {
getActiveDashboard(): ActiveDashboard | null;
getRevision(revisionId: string): DashboardRevision | null;
listRevisions(limit?: number): DashboardRevision[];
seedDashboardIfEmpty(
document: DashboardDocument,
metadata?: DashboardWriteMetadata,
): DashboardRevision;
commitDashboard(
document: DashboardDocument,
metadata?: DashboardWriteMetadata,
): DashboardRevision;
rollbackToRevision(
revisionId: string,
metadata?: DashboardWriteMetadata,
): DashboardRevision;
close(): void;
}
export class DashboardPersistenceValidationError extends Error {
readonly failure: DashboardValidationFailure;
constructor(failure: DashboardValidationFailure) {
super(`Invalid dashboard document: ${failure.errors.join("; ")}`);
this.name = "DashboardPersistenceValidationError";
this.failure = failure;
}
}
export class DashboardRevisionNotFoundError extends Error {
constructor(revisionId: string) {
super(`Dashboard revision not found: ${revisionId}`);
this.name = "DashboardRevisionNotFoundError";
}
}
export function createDashboardStore(
options: DashboardStoreOptions = {},
): DashboardStore {
const connection = openDashboardDatabase(options.databaseUrl);
return new SqliteDashboardStore(connection, options.dashboardId || DEFAULT_DASHBOARD_ID);
}
class SqliteDashboardStore implements DashboardStore {
constructor(
private readonly connection: DashboardDatabaseConnection,
private readonly dashboardId: string,
) {}
getActiveDashboard(): ActiveDashboard | null {
const dashboard = this.connection.db
.select()
.from(dashboardDocuments)
.where(eq(dashboardDocuments.id, this.dashboardId))
.get();
if (!dashboard?.currentRevisionId) return null;
const revision = this.getRevision(dashboard.currentRevisionId);
if (!revision) return null;
return {
dashboardId: dashboard.id,
currentRevisionId: dashboard.currentRevisionId,
document: revision.document,
revision,
createdAt: dashboard.createdAt,
updatedAt: dashboard.updatedAt,
};
}
getRevision(revisionId: string): DashboardRevision | null {
const row = this.connection.db
.select()
.from(dashboardRevisions)
.where(eq(dashboardRevisions.id, revisionId))
.get();
return row ? toRevision(row) : null;
}
listRevisions(limit = 50): DashboardRevision[] {
return this.connection.db
.select()
.from(dashboardRevisions)
.where(eq(dashboardRevisions.dashboardId, this.dashboardId))
.orderBy(desc(dashboardRevisions.createdAt))
.limit(limit)
.all()
.map(toRevision);
}
seedDashboardIfEmpty(
document: DashboardDocument,
metadata: DashboardWriteMetadata = {},
): DashboardRevision {
const active = this.getActiveDashboard();
if (active) return active.revision;
return this.writeRevision(document, "seed", metadata);
}
commitDashboard(
document: DashboardDocument,
metadata: DashboardWriteMetadata = {},
): DashboardRevision {
return this.writeRevision(document, "commit", metadata);
}
rollbackToRevision(
revisionId: string,
metadata: DashboardWriteMetadata = {},
): DashboardRevision {
const revision = this.getRevision(revisionId);
if (!revision || revision.dashboardId !== this.dashboardId) {
throw new DashboardRevisionNotFoundError(revisionId);
}
return this.writeRevision(revision.document, "rollback", metadata, revision.id);
}
close() {
this.connection.close();
}
private writeRevision(
document: DashboardDocument,
operation: DashboardRevisionOperation,
metadata: DashboardWriteMetadata,
sourceRevisionId: string | null = null,
): DashboardRevision {
const migration = migrateDashboardDocumentForPersistence(document);
if (!migration.valid) {
throw new DashboardPersistenceValidationError(migration.failure);
}
const now = this.nextRevisionTimestamp();
const revision: DashboardRevision = {
id: randomUUID(),
dashboardId: this.dashboardId,
schemaVersion: migration.document.schemaVersion,
document: structuredClone(migration.document),
actor: metadata.actor || DEFAULT_ACTOR,
message: metadata.message || null,
operation,
sourceRevisionId,
createdAt: now,
};
this.connection.db.transaction((tx) => {
tx.insert(dashboardRevisions).values({
id: revision.id,
dashboardId: revision.dashboardId,
schemaVersion: revision.schemaVersion,
document: revision.document,
actor: revision.actor,
message: revision.message,
operation: revision.operation,
sourceRevisionId: revision.sourceRevisionId,
createdAt: revision.createdAt,
}).run();
tx.insert(dashboardDocuments)
.values({
id: this.dashboardId,
currentRevisionId: revision.id,
createdAt: now,
updatedAt: now,
})
.onConflictDoUpdate({
target: dashboardDocuments.id,
set: {
currentRevisionId: revision.id,
updatedAt: now,
},
})
.run();
});
return revision;
}
private nextRevisionTimestamp(): Date {
const active = this.getActiveDashboard();
const activeUpdatedAtMs = active?.updatedAt.getTime() || 0;
return new Date(Math.max(Date.now(), activeUpdatedAtMs + 1));
}
}
type DashboardRevisionRow = typeof dashboardRevisions.$inferSelect;
function toRevision(row: DashboardRevisionRow): DashboardRevision {
const migration = migrateDashboardDocumentForPersistence(row.document);
if (!migration.valid) {
throw new DashboardPersistenceValidationError(migration.failure);
}
return {
id: row.id,
dashboardId: row.dashboardId,
schemaVersion: row.schemaVersion,
document: migration.document,
actor: row.actor,
message: row.message,
operation: row.operation as DashboardRevisionOperation,
sourceRevisionId: row.sourceRevisionId,
createdAt: row.createdAt,
};
}

View file

@ -0,0 +1,52 @@
import { existsSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
const MIGRATION_JOURNAL_PATH = join("meta", "_journal.json");
export function applyDashboardMigrations(
database: BunSQLiteDatabase<Record<string, unknown>>,
migrationsFolder = resolveDashboardMigrationsFolder(),
) {
migrate(database, { migrationsFolder });
}
export function resolveDashboardMigrationsFolder(
explicitFolder = process.env.DASHBOARD_MIGRATIONS_DIR,
): string {
if (explicitFolder) return assertMigrationFolder(resolve(explicitFolder));
const cwdFolder = findMigrationFolder(process.cwd());
if (cwdFolder) return cwdFolder;
const moduleFolder = findMigrationFolder(dirname(fileURLToPath(import.meta.url)));
if (moduleFolder) return moduleFolder;
throw new Error(
"Unable to locate dashboard Drizzle migrations. Set DASHBOARD_MIGRATIONS_DIR.",
);
}
function findMigrationFolder(startPath: string): string | null {
let currentPath = resolve(startPath);
while (true) {
const candidate = join(currentPath, "drizzle");
if (hasMigrationJournal(candidate)) return candidate;
const parentPath = dirname(currentPath);
if (parentPath === currentPath) return null;
currentPath = parentPath;
}
}
function assertMigrationFolder(folder: string): string {
if (hasMigrationJournal(folder)) return folder;
throw new Error(`Dashboard migrations not found in ${folder}`);
}
function hasMigrationJournal(folder: string): boolean {
return existsSync(join(folder, MIGRATION_JOURNAL_PATH));
}

View file

@ -0,0 +1,31 @@
import { describe, expect, test } from "vitest";
import { DASHBOARD_SCHEMA_VERSION } from "$lib/model";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import {
UnsupportedDashboardModelVersionError,
migrateDashboardDocumentForPersistence,
} from "./model-migrations";
describe("dashboard model migrations", () => {
test("accepts the current dashboard model version without migration", () => {
const result = migrateDashboardDocumentForPersistence(genericDashboardFixture);
expect(result.valid).toBe(true);
if (!result.valid) throw new Error("expected current fixture to be valid");
expect(result.document).toEqual(genericDashboardFixture);
expect(result.fromVersion).toBe(DASHBOARD_SCHEMA_VERSION);
expect(result.toVersion).toBe(DASHBOARD_SCHEMA_VERSION);
expect(result.migrated).toBe(false);
});
test("fails unsupported model versions with an explicit migration error", () => {
const previousVersion = {
...genericDashboardFixture,
schemaVersion: "dashboard.v0",
};
expect(() => migrateDashboardDocumentForPersistence(previousVersion)).toThrow(
UnsupportedDashboardModelVersionError,
);
});
});

View file

@ -0,0 +1,62 @@
import {
DASHBOARD_SCHEMA_VERSION,
validateDashboardDocument,
type DashboardDocument,
type DashboardValidationFailure,
} from "$lib/model";
export interface DashboardModelMigrationSuccess {
valid: true;
document: DashboardDocument;
fromVersion: string;
toVersion: typeof DASHBOARD_SCHEMA_VERSION;
migrated: boolean;
}
export interface DashboardModelMigrationFailure {
valid: false;
failure: DashboardValidationFailure;
}
export type DashboardModelMigrationResult =
| DashboardModelMigrationFailure
| DashboardModelMigrationSuccess;
export class UnsupportedDashboardModelVersionError extends Error {
constructor(
readonly fromVersion: string,
readonly toVersion: string,
) {
super(`No dashboard model migration from ${fromVersion} to ${toVersion}`);
this.name = "UnsupportedDashboardModelVersionError";
}
}
export function migrateDashboardDocumentForPersistence(
value: unknown,
): DashboardModelMigrationResult {
const version = readSchemaVersion(value);
if (version && version !== DASHBOARD_SCHEMA_VERSION) {
throw new UnsupportedDashboardModelVersionError(version, DASHBOARD_SCHEMA_VERSION);
}
const validation = validateDashboardDocument(value);
if (!validation.valid) {
return { valid: false, failure: validation };
}
return {
valid: true,
document: validation.data,
fromVersion: DASHBOARD_SCHEMA_VERSION,
toVersion: DASHBOARD_SCHEMA_VERSION,
migrated: false,
};
}
function readSchemaVersion(value: unknown): string | null {
if (!value || typeof value !== "object") return null;
const schemaVersion = (value as { schemaVersion?: unknown }).schemaVersion;
return typeof schemaVersion === "string" ? schemaVersion : null;
}

View file

@ -0,0 +1,39 @@
import type { DashboardDocument } from "$lib/model";
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const dashboardDocuments = sqliteTable("dashboard_documents", {
id: text("id").primaryKey(),
currentRevisionId: text("current_revision_id"),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
});
export const dashboardRevisions = sqliteTable(
"dashboard_revisions",
{
id: text("id").primaryKey(),
dashboardId: text("dashboard_id").notNull(),
schemaVersion: text("schema_version").notNull(),
document: text("document", { mode: "json" }).$type<DashboardDocument>().notNull(),
actor: text("actor").notNull(),
message: text("message"),
operation: text("operation", {
enum: ["seed", "commit", "rollback"],
}).notNull(),
sourceRevisionId: text("source_revision_id"),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
},
(table) => [
index("idx_dashboard_revisions_dashboard_created").on(
table.dashboardId,
table.createdAt,
),
],
);
export const dashboardDbSchema = {
dashboardDocuments,
dashboardRevisions,
};
export type DashboardRevisionOperation = "seed" | "commit" | "rollback";

View file

@ -0,0 +1,55 @@
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
import { externalApiHandlers } from "./external-api-mocks";
const server = setupServer(...externalApiHandlers);
describe("external API mocks", () => {
beforeAll(() => {
server.listen({ onUnhandledRequest: "error" });
});
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
server.close();
});
test("defines deterministic handlers for deferred datasource adapters", () => {
expect(externalApiHandlers).toHaveLength(3);
expect(externalApiHandlers.map((handler) => handler.info.header)).toEqual([
"GET https://prometheus.dimensionlab.net/api/v1/query",
"GET https://uptime.dimensionlab.net/api/status-page/*",
"GET https://api.open-meteo.com/v1/forecast",
]);
});
test("intercepts deferred datasource requests without live services", async () => {
const prometheus = await fetch(
"https://prometheus.dimensionlab.net/api/v1/query",
).then((response) => response.json());
const status = await fetch(
"https://uptime.dimensionlab.net/api/status-page/dimensionlab",
).then((response) => response.json());
const weather = await fetch(
"https://api.open-meteo.com/v1/forecast?latitude=52.37&longitude=4.9",
).then((response) => response.json());
expect(prometheus).toMatchObject({
status: "success",
data: { result: [{ value: [1771430400, "1"] }] },
});
expect(status).toMatchObject({
status: "ok",
incidents: [],
});
expect(weather).toMatchObject({
current: {
temperature_2m: 21.4,
weather_code: 0,
},
});
});
});

View file

@ -0,0 +1,33 @@
import { http, HttpResponse } from "msw";
export const externalApiHandlers = [
http.get("https://prometheus.dimensionlab.net/api/v1/query", () =>
HttpResponse.json({
status: "success",
data: {
resultType: "vector",
result: [
{
metric: { instance: "fixture" },
value: [1771430400, "1"],
},
],
},
}),
),
http.get("https://uptime.dimensionlab.net/api/status-page/*", () =>
HttpResponse.json({
status: "ok",
incidents: [],
monitors: [{ name: "fixture", status: "up" }],
}),
),
http.get("https://api.open-meteo.com/v1/forecast", () =>
HttpResponse.json({
current: {
temperature_2m: 21.4,
weather_code: 0,
},
}),
),
];

View file

@ -0,0 +1,102 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { type DashboardDocument } from "$lib/model";
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { dashboardDocumentToUiDashboard } from "./model-renderer";
const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
? process.cwd()
: join(process.cwd(), "apps", "web");
describe("dashboard model renderer", () => {
test("projects the Dimension Lab model into UI component props", () => {
const dashboard = dashboardDocumentToUiDashboard(dimensionLabDashboardFixture);
expect(dashboard.title).toBe(dimensionLabDashboardFixture.metadata.title);
expect(dashboard.subtitle).toBe(dimensionLabDashboardFixture.metadata.subtitle);
expect(dashboard.telemetry.map((card) => card.id)).toEqual(
dimensionLabDashboardFixture.layout.telemetry,
);
expect(dashboard.telemetry[0]).toMatchObject({
label: "Infra RAM",
icon: "mdi:memory",
severity: "ok",
});
expect(dashboard.serviceGroups.map((group) => group.id)).toEqual(
dimensionLabDashboardFixture.layout.serviceGroups,
);
expect(dashboard.serviceGroups.find((group) => group.id === "runtime-health")?.layout).toBe(
"grid",
);
expect(dashboard.modules.map((module) => module.id)).toEqual([
"weather-amsterdam",
"runtime-health-summary",
]);
expect(dashboard.statusItems.map((item) => item.id)).toEqual([
"footer-status:system-status",
"footer-status:last-sync",
"footer-status:uptime",
"footer-status:load-avg",
"footer-status:auto-refresh",
]);
expect(dashboard.statusStripId).toBe("footer-status");
});
test("projects a second fixture through the same mapper", () => {
const dashboard = dashboardDocumentToUiDashboard(genericDashboardFixture);
expect(dashboard.title).toBe("Operations Console");
expect(dashboard.telemetry.map((card) => card.id)).toEqual([
"service-uptime",
"queue-depth",
]);
expect(dashboard.serviceGroups[0]?.services.map((service) => service.id)).toEqual([
"identity",
"scheduler",
]);
expect(dashboard.modules[0]).toMatchObject({
id: "ambient",
title: "Environment",
icon: "mdi:radar",
});
});
test("returns intentional empty arrays for missing optional sections", () => {
const emptyModel: DashboardDocument = {
...genericDashboardFixture,
layout: {
density: "compact",
telemetry: [],
serviceGroups: [],
statusStrips: [],
modules: [],
},
telemetry: [],
serviceGroups: [],
statusStrips: [],
modules: [],
};
const dashboard = dashboardDocumentToUiDashboard(emptyModel);
expect(dashboard.telemetry).toEqual([]);
expect(dashboard.serviceGroups).toEqual([]);
expect(dashboard.statusItems).toEqual([]);
expect(dashboard.modules).toEqual([]);
});
test("does not hardcode environment-specific content in mapper source", () => {
const source = readFileSync(
join(appRoot, "src/lib/ui-adapter/model-renderer.ts"),
"utf8",
)
.toLowerCase()
.replaceAll("@dimensionlab/ui", "@internal/ui");
expect(source).not.toContain("dimension");
expect(source).not.toContain("vaultwarden");
expect(source).not.toContain("forgejo");
});
});

View file

@ -0,0 +1,111 @@
import type {
DashboardDocument,
DashboardModule,
ServiceEntry,
ServiceGroup,
StatusItem,
StatusStrip,
TelemetryCard,
} from "$lib/model";
import type {
UiDashboardPreview,
UiModuleBlock,
UiServiceGroup,
UiServiceRow,
UiStatusItem,
UiTelemetryCard,
} from "@dimensionlab/ui";
export function dashboardDocumentToUiDashboard(
document: DashboardDocument,
): UiDashboardPreview {
return {
eyebrow: document.schemaVersion,
title: document.metadata.title,
subtitle: document.metadata.subtitle,
telemetry: orderedItems(document.layout.telemetry, document.telemetry).map(
telemetryToUi,
),
serviceGroups: orderedItems(
document.layout.serviceGroups,
document.serviceGroups,
).map(serviceGroupToUi),
modules: orderedItems(
document.layout.modules || [],
document.modules || [],
).map(moduleToUi),
statusItems: orderedItems(
document.layout.statusStrips,
document.statusStrips,
).flatMap(statusStripToUiItems),
statusStripId: document.layout.statusStrips[0],
};
}
function telemetryToUi(card: TelemetryCard): UiTelemetryCard {
return {
id: card.id,
label: card.label,
description: card.description,
icon: card.icon,
value: card.value,
detail: card.detail,
severity: card.severity,
sparkline: card.sparkline,
};
}
function serviceGroupToUi(group: ServiceGroup): UiServiceGroup {
return {
id: group.id,
layout: group.layout,
title: group.title,
services: group.services.map(serviceToUi),
};
}
function serviceToUi(service: ServiceEntry): UiServiceRow {
return {
id: service.id,
label: service.label,
description: service.description,
icon: service.icon,
severity: service.severity,
detail: service.detail,
link: service.link,
};
}
function moduleToUi(module: DashboardModule): UiModuleBlock {
return {
id: module.id,
title: module.title,
label: module.label,
value: module.value,
detail: module.detail,
icon: module.icon,
severity: module.severity,
};
}
function statusStripToUiItems(strip: StatusStrip): UiStatusItem[] {
return strip.items.map((item) => statusItemToUi(strip.id, item));
}
function statusItemToUi(stripId: string, item: StatusItem): UiStatusItem {
return {
id: `${stripId}:${item.id}`,
label: item.label,
value: item.value,
severity: item.severity,
link: item.link,
};
}
function orderedItems<T extends { id: string }>(ids: string[], items: T[]): T[] {
const byId = new Map(items.map((item) => [item.id, item]));
return ids.flatMap((id) => {
const item = byId.get(id);
return item ? [item] : [];
});
}

View file

@ -2,7 +2,9 @@ import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
const root = process.cwd();
const root = existsSync(join(process.cwd(), "turbo.json"))
? process.cwd()
: join(process.cwd(), "..", "..");
describe("workspace boundaries", () => {
test("declares the root as a turbo-managed bun workspace", () => {

13
apps/web/src/main.tsx Normal file
View file

@ -0,0 +1,13 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./app.css";
const root = document.getElementById("root");
if (!root) throw new Error("Missing React root element");
createRoot(root).render(
<StrictMode>
<App />
</StrictMode>,
);

View file

@ -0,0 +1,97 @@
import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { AppStateView, resolveDocumentMetadata } from "./App";
describe("home page model renderer", () => {
test("renders the active dashboard model from the runtime state", () => {
const body = renderToString(
<AppStateView
dashboard={{
state: "ready",
document: genericDashboardFixture,
schemaVersion: "dashboard.v1",
currentRevisionId: "revision-1234567890",
}}
/>,
);
expect(body).toContain("Operations Console");
expect(body).toContain("Service Uptime");
expect(body).toContain("Identity");
expect(body).toContain("data-model-id=\"service-uptime\"");
expect(body).not.toContain("primary");
expect(body).not.toContain("secondary");
});
test("renders invalid model state without crashing", () => {
const body = renderToString(
<AppStateView
dashboard={{
state: "invalid",
title: "Invalid Dashboard",
subtitle: "Validation failed",
message: "Dashboard document is invalid.",
errors: ["/metadata/title is required"],
}}
/>,
);
expect(body).toContain("Invalid Dashboard");
expect(body).toContain("Validation failed");
expect(body).toContain("Dashboard document is invalid.");
expect(body).toContain("/metadata/title is required");
});
test("derives browser metadata from ready and fallback runtime states", () => {
const ready = resolveDocumentMetadata({
state: "ready",
document: genericDashboardFixture,
schemaVersion: "dashboard.v1",
currentRevisionId: "revision-1234567890",
});
const empty = resolveDocumentMetadata({
state: "empty",
title: "No Dashboard Model",
subtitle: "No active document",
message: "No validated dashboard document is active yet.",
});
expect(ready).toEqual({
title: "Operations Console",
description: "Generic environment",
});
expect(empty).toEqual({
title: "No Dashboard Model",
description: "No active document",
});
});
test("renders empty and loading model states without crashing", () => {
const empty = renderToString(
<AppStateView
dashboard={{
state: "empty",
title: "No Dashboard Model",
subtitle: "No active document",
message: "No validated dashboard document is active yet.",
}}
/>,
);
const loading = renderToString(
<AppStateView
dashboard={{
state: "loading",
title: "Loading Dashboard",
subtitle: "Fetching active model",
message: "Waiting for the active dashboard document.",
}}
/>,
);
expect(empty).toContain("No Dashboard Model");
expect(empty).toContain("No active document");
expect(loading).toContain("Loading Dashboard");
expect(loading).toContain("Fetching active model");
});
});

View file

@ -0,0 +1,25 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { createDevServerConfig } from "../../vite.config";
describe("local development runtime", () => {
test("starts the Bun API server together with the Vite dev server", () => {
const packageJson = JSON.parse(
readFileSync(join(process.cwd(), "package.json"), "utf8"),
) as { scripts?: Record<string, string> };
expect(packageJson.scripts?.dev).toBe("bun src/server/dev.ts");
});
test("proxies dashboard API requests from Vite to the Bun API server", () => {
const server = createDevServerConfig({
DASHBOARD_DEV_API_TARGET: "http://127.0.0.1:5174",
});
expect(server?.proxy?.["/api"]).toMatchObject({
target: "http://127.0.0.1:5174",
changeOrigin: true,
});
});
});

View file

@ -0,0 +1,72 @@
const webHost = process.env.HOST || "0.0.0.0";
const webPort = process.env.PORT || "5173";
const apiHost = process.env.DASHBOARD_DEV_API_HOST || "127.0.0.1";
const apiPort = process.env.DASHBOARD_DEV_API_PORT || "5174";
const apiTarget = `http://${apiHost}:${apiPort}`;
if (import.meta.main) {
runDevServers();
}
export function runDevServers(): void {
const children: Array<ReturnType<typeof Bun.spawn>> = [];
let shuttingDown = false;
function spawn(
label: string,
command: string[],
env: Record<string, string> = {},
): void {
const child = Bun.spawn(command, {
env: {
...process.env,
...env,
},
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
});
children.push(child);
void child.exited.then((code) => {
if (shuttingDown) return;
console.error(`${label} exited with status ${code}`);
shutdown(code || 1);
});
}
function shutdown(code = 0): void {
if (shuttingDown) return;
shuttingDown = true;
for (const child of children) {
child.kill();
}
void Promise.allSettled(children.map((child) => child.exited)).then(() => {
process.exit(code);
});
}
process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0));
spawn("api server", [process.execPath, "src/server/index.ts"], {
HOST: apiHost,
PORT: apiPort,
});
spawn("vite dev server", [
process.execPath,
"x",
"vite",
"--host",
webHost,
"--port",
webPort,
], {
DASHBOARD_DEV_API_TARGET: apiTarget,
});
console.info(`Dashboard API proxy target: ${apiTarget}`);
}

View file

@ -0,0 +1,84 @@
import { extname, normalize } from "node:path";
import { handleAgentDashboardRoute } from "./routes/agent-dashboard";
import { handleDashboardRoute } from "./routes/dashboard";
const host = process.env.HOST || "0.0.0.0";
const port = Number(process.env.PORT || 3000);
const distRoot = `${process.cwd()}/dist`;
const contentTypes = new Map([
[".css", "text/css; charset=utf-8"],
[".html", "text/html; charset=utf-8"],
[".js", "text/javascript; charset=utf-8"],
[".json", "application/json; charset=utf-8"],
[".svg", "image/svg+xml"],
[".wasm", "application/wasm"],
]);
export async function handleRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/dashboard") {
if (request.method !== "GET") return methodNotAllowed(["GET"]);
return handleDashboardRoute();
}
if (url.pathname === "/api/agent/dashboard") {
if (request.method !== "POST") return methodNotAllowed(["POST"]);
return handleAgentDashboardRoute(request);
}
if (url.pathname.startsWith("/api/")) {
return Response.json({ ok: false, message: "Not found" }, { status: 404 });
}
return serveStaticAsset(url.pathname);
}
async function serveStaticAsset(pathname: string): Promise<Response> {
const safePath = normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, "");
const assetPath = safePath === "/" || safePath === "." ? "/index.html" : safePath;
const file = Bun.file(`${distRoot}${assetPath}`);
if (await file.exists()) {
return new Response(file, {
headers: contentTypeHeaders(assetPath),
});
}
const fallback = Bun.file(`${distRoot}/index.html`);
if (await fallback.exists()) {
return new Response(fallback, {
headers: contentTypeHeaders(".html"),
});
}
return new Response("Build output not found", { status: 404 });
}
function methodNotAllowed(allowedMethods: string[]): Response {
return Response.json(
{ ok: false, message: "Method not allowed" },
{
status: 405,
headers: {
Allow: allowedMethods.join(", "),
},
},
);
}
function contentTypeHeaders(pathname: string): HeadersInit {
const contentType = contentTypes.get(extname(pathname));
return contentType ? { "Content-Type": contentType } : {};
}
if (import.meta.main) {
Bun.serve({
hostname: host,
port,
fetch: handleRequest,
});
console.info(`Dimension Lab website listening on http://${host}:${port}`);
}

View file

@ -0,0 +1,12 @@
import { describe, expect, test } from "vitest";
import { handleAgentDashboardRoute } from "./agent-dashboard";
describe("agent dashboard API route", () => {
test("delegates unauthorized requests to the existing agent handler", async () => {
const response = await handleAgentDashboardRoute(
new Request("http://localhost/api/agent/dashboard", { method: "POST" }),
);
expect(response.status).toBe(401);
});
});

View file

@ -0,0 +1,5 @@
import { handleAgentDashboardRequest } from "$lib/server/agent-config";
export function handleAgentDashboardRoute(request: Request): Promise<Response> {
return handleAgentDashboardRequest(request);
}

View file

@ -0,0 +1,19 @@
import { describe, expect, test } from "vitest";
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
import { loadDashboardResponse } from "./dashboard";
describe("dashboard API route", () => {
test("returns ready dashboard runtime state from the existing model loader", async () => {
const response = await loadDashboardResponse({
disableLiveDatasources: true,
refreshSeedDocument: true,
seedIfEmpty: true,
});
expect(response.state).toBe("ready");
if (response.state !== "ready") throw new Error("expected ready dashboard");
expect(response.document.metadata.title).toBe(
dimensionLabDashboardFixture.metadata.title,
);
});
});

View file

@ -0,0 +1,41 @@
import {
loadDashboardRuntime,
type DashboardRuntimeOptions,
type DashboardRuntimeState,
} from "$lib/server/dashboard";
import { resolveDashboardDatasources } from "$lib/server/datasources";
export interface LoadDashboardResponseOptions
extends Pick<
DashboardRuntimeOptions,
"refreshSeedDocument" | "seedIfEmpty" | "seedDocument"
> {
disableLiveDatasources?: boolean;
}
export async function loadDashboardResponse(
options: LoadDashboardResponseOptions = {},
): Promise<DashboardRuntimeState> {
const dashboard = loadDashboardRuntime(undefined, {
refreshSeedDocument: options.refreshSeedDocument ?? true,
seedIfEmpty: options.seedIfEmpty ?? true,
seedDocument: options.seedDocument,
});
if (dashboard.state !== "ready") {
return dashboard;
}
if (options.disableLiveDatasources || process.env.DISABLE_LIVE_DATASOURCES === "1") {
return dashboard;
}
return {
...dashboard,
document: await resolveDashboardDatasources(dashboard.document),
};
}
export async function handleDashboardRoute(): Promise<Response> {
return Response.json(await loadDashboardResponse());
}

3
apps/web/src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
/// <reference types="vite/client" />
declare module "*.css" {}

View file

@ -0,0 +1,349 @@
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
*/
const PACKAGE_VERSION = '2.14.6'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
addEventListener('install', function () {
self.skipWaiting()
})
addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
addEventListener('message', async function (event) {
const clientId = Reflect.get(event.source || {}, 'id')
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
addEventListener('fetch', function (event) {
const requestInterceptedAt = Date.now()
// Bypass navigation requests.
if (event.request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (
event.request.cache === 'only-if-cached' &&
event.request.mode !== 'same-origin'
) {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been terminated (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
})
/**
* @param {FetchEvent} event
* @param {string} requestId
* @param {number} requestInterceptedAt
*/
async function handleRequest(event, requestId, requestInterceptedAt) {
const client = await resolveMainClient(event)
const requestCloneForEvents = event.request.clone()
const response = await getResponse(
event,
client,
requestId,
requestInterceptedAt,
)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
const serializedRequest = await serializeRequest(requestCloneForEvents)
// Clone the response so both the client and the library could consume it.
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
isMockedResponse: IS_MOCKED_RESPONSE in response,
request: {
id: requestId,
...serializedRequest,
},
response: {
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
headers: Object.fromEntries(responseClone.headers.entries()),
body: responseClone.body,
},
},
},
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
)
}
return response
}
/**
* Resolve the main client for the given event.
* Client that issues a request doesn't necessarily equal the client
* that registered the worker. It's with the latter the worker should
* communicate with during the response resolving phase.
* @param {FetchEvent} event
* @returns {Promise<Client | undefined>}
*/
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
/**
* @param {FetchEvent} event
* @param {Client | undefined} client
* @param {string} requestId
* @param {number} requestInterceptedAt
* @returns {Promise<Response>}
*/
async function getResponse(event, client, requestId, requestInterceptedAt) {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = event.request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const serializedRequest = await serializeRequest(event.request)
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
interceptedAt: requestInterceptedAt,
...serializedRequest,
},
},
[serializedRequest.body],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
/**
* @param {Client} client
* @param {any} message
* @param {Array<Transferable>} transferrables
* @returns {Promise<any>}
*/
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(message, [
channel.port2,
...transferrables.filter(Boolean),
])
})
}
/**
* @param {Response} response
* @returns {Response}
*/
function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
/**
* @param {Request} request
*/
async function serializeRequest(request) {
return {
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.arrayBuffer(),
keepalive: request.keepalive,
}
}

View file

@ -0,0 +1,382 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Page } from "@playwright/test";
const linkedServiceIds = [
"vaultwarden",
"forgejo",
"wiki",
"aws-start",
"adguard-primary",
"adguard-secondary",
"grafana",
"uptime-kuma",
"prometheus",
"backrest",
"n8n",
"open-webui",
"comfyui",
"models",
"prompt-registry",
"adminer",
"assistant",
"suna",
"cockpit-infra",
"cockpit-gpu",
"cockpit-network-core",
"forgejo-ssh-relay",
];
test.describe("dashboard page QA gate", () => {
test("renders the model-driven dashboard on desktop", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.goto("/");
await waitForDashboardReady(page);
await expect(page.getByRole("main")).toHaveAttribute(
"aria-labelledby",
/title/,
);
await expect(
page.getByRole("heading", { level: 1, name: "System Overview" }),
).toBeVisible();
await expect(page.getByText("Infra RAM")).toBeVisible();
await expect(page.getByRole("link", { name: /Vaultwarden/i })).toBeVisible();
await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();
const bodyBox = await page.locator("body").boundingBox();
expect(bodyBox?.width).toBeGreaterThan(1000);
await expect(page).toHaveScreenshot("dashboard-desktop.png", {
fullPage: true,
});
});
test("fits the operational dashboard into a 1470 by 956 viewport", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.setViewportSize({ width: 1470, height: 956 });
await page.goto("/");
await waitForDashboardReady(page);
const metrics = await page.evaluate(() => {
const footer = document.querySelector("[data-model-id='footer-status']");
const runtime = document.querySelector("[data-model-id='runtime-health']");
const visibleItems = [
...document.querySelectorAll(".telemetry-card"),
...document.querySelectorAll(".service-row"),
...document.querySelectorAll(".footer-cell"),
].map((element) => {
const rect = element.getBoundingClientRect();
return {
bottom: rect.bottom,
height: rect.height,
id: element.getAttribute("data-model-id"),
top: rect.top,
width: rect.width,
};
});
return {
clippedItems: visibleItems.filter((item) =>
item.top < 0 ||
item.bottom > window.innerHeight ||
item.width <= 0 ||
item.height <= 0
),
footerCellCount: document.querySelectorAll(".footer-cell").length,
scrollWidth: document.documentElement.scrollWidth,
scrollHeight: document.documentElement.scrollHeight,
serviceRowCount: document.querySelectorAll(".service-row").length,
telemetryCardCount: document.querySelectorAll(".telemetry-card").length,
footerBottom: footer?.getBoundingClientRect().bottom ?? 0,
runtimeBottom: runtime?.getBoundingClientRect().bottom ?? 0,
};
});
expect(metrics.scrollWidth).toBeLessThanOrEqual(1470);
expect(metrics.scrollHeight).toBeLessThanOrEqual(956);
expect(metrics.runtimeBottom).toBeLessThanOrEqual(956);
expect(metrics.footerBottom).toBeLessThanOrEqual(956);
expect(metrics.telemetryCardCount).toBe(16);
expect(metrics.serviceRowCount).toBe(28);
expect(metrics.footerCellCount).toBe(5);
expect(metrics.clippedItems).toEqual([]);
});
test("keeps intermediate viewports scrollable without horizontal clipping", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
for (const viewport of [
{ width: 900, height: 956 },
{ width: 1024, height: 768 },
]) {
await page.setViewportSize(viewport);
await page.goto("/");
await waitForDashboardReady(page);
const metrics = await page.evaluate(() => {
const footer = document.querySelector("[data-model-id='footer-status']");
const trackedElements = [
document.querySelector(".dashboard-frame__header"),
...document.querySelectorAll(".telemetry-card"),
...document.querySelectorAll(".service-panel"),
document.querySelector("[data-model-id='runtime-health']"),
footer,
].filter((element): element is Element => Boolean(element));
const footerRect = footer?.getBoundingClientRect();
const clippedRight = trackedElements
.map((element) => {
const rect = element.getBoundingClientRect();
return {
id: element.getAttribute("data-model-id") || element.className,
right: rect.right,
width: rect.width,
};
})
.filter((item) => item.right > window.innerWidth + 1 || item.width <= 0);
return {
clippedRight,
footerBottomInDocument: (footerRect?.bottom ?? 0) + window.scrollY,
frameOverflow: window.getComputedStyle(
document.querySelector(".dashboard-frame") as Element,
).overflow,
scrollHeight: document.documentElement.scrollHeight,
scrollWidth: document.documentElement.scrollWidth,
};
});
expect(metrics.scrollWidth).toBeLessThanOrEqual(viewport.width);
expect(metrics.scrollHeight).toBeGreaterThanOrEqual(
Math.ceil(metrics.footerBottomInDocument),
);
expect(metrics.frameOverflow).not.toBe("hidden");
expect(metrics.clippedRight).toEqual([]);
}
});
test("renders bookmark rows as a compact divider list", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.goto("/");
await waitForDashboardReady(page);
const bookmarkDensity = await page.evaluate(() => {
const panelBody = document.querySelector(".service-panel .panel__body");
const rows = Array.from(document.querySelectorAll<HTMLElement>(".service-panel .service-row"));
const first = rows[0];
const second = rows[1];
if (!panelBody || !first || !second) {
throw new Error("expected at least two bookmark rows");
}
const bodyStyle = window.getComputedStyle(panelBody);
const rowStyle = window.getComputedStyle(first);
const firstRect = first.getBoundingClientRect();
const secondRect = second.getBoundingClientRect();
return {
backgroundColor: rowStyle.backgroundColor,
borderBottomWidth: Number.parseFloat(rowStyle.borderBottomWidth),
borderLeftWidth: Number.parseFloat(rowStyle.borderLeftWidth),
borderRightWidth: Number.parseFloat(rowStyle.borderRightWidth),
columnGap: Number.parseFloat(rowStyle.columnGap),
paddingBottom: Number.parseFloat(rowStyle.paddingBottom),
paddingTop: Number.parseFloat(rowStyle.paddingTop),
panelGap: Number.parseFloat(bodyStyle.rowGap),
rowGap: secondRect.top - firstRect.bottom,
};
});
expect(bookmarkDensity.panelGap).toBe(0);
expect(bookmarkDensity.rowGap).toBeLessThanOrEqual(1);
expect(bookmarkDensity.paddingTop).toBeLessThanOrEqual(3);
expect(bookmarkDensity.paddingBottom).toBeLessThanOrEqual(3);
expect(bookmarkDensity.columnGap).toBeLessThanOrEqual(4);
expect(bookmarkDensity.borderBottomWidth).toBeGreaterThanOrEqual(1);
expect(bookmarkDensity.borderLeftWidth).toBe(0);
expect(bookmarkDensity.borderRightWidth).toBe(0);
expect(bookmarkDensity.backgroundColor).toBe("rgba(0, 0, 0, 0)");
});
test("balances dashboard typography at the target viewport", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.setViewportSize({ width: 1470, height: 956 });
await page.goto("/");
await waitForDashboardReady(page);
const typeScale = await page.evaluate(() => {
const fontSize = (selector: string) => {
const element = document.querySelector(selector);
if (!element) throw new Error(`missing ${selector}`);
return Number.parseFloat(window.getComputedStyle(element).fontSize);
};
return {
h1: fontSize("h1"),
panelTitle: fontSize(".service-panel h2"),
serviceDescription: fontSize(".service-row p"),
serviceLabel: fontSize(".service-row h3"),
telemetryLabel: fontSize(".telemetry-card h3"),
telemetryValue: fontSize(".telemetry-card strong"),
};
});
expect(typeScale.serviceLabel).toBeGreaterThanOrEqual(11.4);
expect(typeScale.serviceDescription).toBeGreaterThanOrEqual(8.8);
expect(typeScale.h1).toBeLessThanOrEqual(49);
expect(typeScale.panelTitle).toBeLessThanOrEqual(28);
expect(typeScale.telemetryLabel).toBeLessThanOrEqual(9.5);
expect(typeScale.telemetryValue).toBeLessThanOrEqual(36);
});
test("keeps the first screen usable on mobile", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "chromium-mobile");
await page.goto("/");
await waitForDashboardReady(page);
await expect(
page.getByRole("heading", { level: 1, name: "System Overview" }),
).toBeVisible();
await expect(page.getByLabel("Service groups")).toBeVisible();
await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();
await expect(page).toHaveScreenshot("dashboard-mobile.png", {
fullPage: true,
});
});
test("exposes usable landmarks and a visible keyboard focus state", async ({
page,
}) => {
await page.goto("/");
await waitForDashboardReady(page);
await expect(page.getByRole("main")).toHaveCount(1);
await page.keyboard.press("Tab");
const themeToggle = page.getByRole("button", { name: "Light theme" });
await expect(themeToggle).toBeFocused();
await expect(themeToggle).toHaveAttribute("aria-pressed", "false");
const themeFocusBoxShadow = await themeToggle.evaluate((element) => {
return window.getComputedStyle(element).boxShadow;
});
expect(themeFocusBoxShadow).not.toBe("none");
for (const serviceId of linkedServiceIds) {
await page.keyboard.press("Tab");
const focused = page.locator(":focus");
await expect(focused).toHaveAttribute("data-model-id", serviceId);
const focusBoxShadow = await focused.evaluate((element) => {
return window.getComputedStyle(element).boxShadow;
});
expect(focusBoxShadow).not.toBe("none");
}
});
test("passes automated accessibility checks", async ({ page }) => {
await page.goto("/");
await waitForDashboardReady(page);
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test("passes automated accessibility checks in light mode", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
await page.goto("/");
await waitForDashboardReady(page);
await page.getByRole("button", { name: "Light theme" }).click();
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
await expect(page).toHaveScreenshot("dashboard-light-desktop.png", {
fullPage: true,
});
});
test("honors reduced-motion preferences", async ({ page }) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await page.goto("/");
await waitForDashboardReady(page);
const durations = await page.evaluate(() => {
const element = document.createElement("div");
element.style.animation = "qa-motion-check 10s infinite";
element.style.transition = "opacity 10s linear";
document.body.append(element);
const styles = window.getComputedStyle(element);
return {
animation: styles.animationDuration,
transition: styles.transitionDuration,
};
});
expect(cssDurationToMilliseconds(durations.animation)).toBeLessThanOrEqual(
0.01,
);
expect(cssDurationToMilliseconds(durations.transition)).toBeLessThanOrEqual(
0.01,
);
});
test("toggles the dashboard between dark and light themes", async ({ page }) => {
await page.goto("/");
await waitForDashboardReady(page);
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "dark");
const switchToLight = page.getByRole("button", {
name: "Light theme",
});
await expect(switchToLight).toBeVisible();
await expect(switchToLight).toHaveAttribute("aria-pressed", "false");
await switchToLight.click();
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
await page.reload();
await waitForDashboardReady(page);
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
});
});
async function waitForDashboardReady(page: Page): Promise<void> {
await expect(
page.getByRole("heading", { level: 1, name: "System Overview" }),
).toBeVisible();
await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
}
function cssDurationToMilliseconds(duration: string): number {
if (duration.endsWith("ms")) return Number.parseFloat(duration);
if (duration.endsWith("s")) return Number.parseFloat(duration) * 1000;
return Number.NaN;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

View file

@ -0,0 +1,35 @@
import { existsSync } from "node:fs";
import { resolve, sep } from "node:path";
const root = resolve(process.cwd(), "packages/ui/storybook-static");
const port = Number(process.env.STORYBOOK_STATIC_PORT || 6007);
if (!existsSync(resolve(root, "iframe.html"))) {
throw new Error(
"packages/ui/storybook-static is missing. Run bun run build-storybook first.",
);
}
Bun.serve({
hostname: "127.0.0.1",
port,
async fetch(request) {
const url = new URL(request.url);
const pathname = decodeURIComponent(url.pathname);
const relativePath = pathname === "/" ? "/index.html" : pathname;
const filePath = resolve(root, `.${relativePath}`);
if (!filePath.startsWith(`${root}${sep}`)) {
return new Response("Forbidden", { status: 403 });
}
const file = Bun.file(filePath);
if (!(await file.exists())) {
return new Response("Not found", { status: 404 });
}
return new Response(file);
},
});
console.log(`Storybook static listening on http://127.0.0.1:${port}`);

View file

@ -0,0 +1,40 @@
import { expect, test } from "@playwright/test";
const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007);
const storybookURL = `http://127.0.0.1:${storybookPort}`;
test.describe("Storybook theme QA", () => {
test("renders the ThemeToggle light story on the light canvas", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name !== "chromium-desktop");
const consoleMessages: string[] = [];
page.on("console", (message) => {
if (["error", "warning"].includes(message.type())) {
consoleMessages.push(message.text());
}
});
const url = `${storybookURL}/iframe.html?id=ui-themetoggle--light&viewMode=story`;
await page.goto(url, { waitUntil: "networkidle" });
await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
await expect(
page.getByRole("button", { name: "Light theme" }),
).toBeVisible();
await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
"aria-pressed",
"true",
);
await expect(page.locator("body")).toHaveCSS(
"background-color",
"rgb(238, 242, 231)",
);
expect(
consoleMessages.filter((message) =>
message.includes("Global args/argTypes can only be set globally"),
),
).toEqual([]);
});
});

View file

@ -3,6 +3,8 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@dimensionlab/ui": ["../../packages/ui/src/index.ts"],
"@dimensionlab/ui/styles.css": ["../../packages/ui/src/styles.css"],
"$lib/*": ["src/lib/*"]
},
"types": ["node", "bun-types", "react", "react-dom", "vite/client"]

40
apps/web/vite.config.ts Normal file
View file

@ -0,0 +1,40 @@
import { fileURLToPath } from "node:url";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig, configDefaults } from "vitest/config";
import type { UserConfig } from "vite";
export function createDevServerConfig(
env: NodeJS.ProcessEnv = process.env,
): UserConfig["server"] {
const apiTarget = env.DASHBOARD_DEV_API_TARGET;
if (!apiTarget) return undefined;
return {
proxy: {
"/api": {
target: apiTarget,
changeOrigin: true,
},
},
};
}
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@dimensionlab/ui/styles.css": fileURLToPath(
new URL("../../packages/ui/src/styles.css", import.meta.url),
),
"@dimensionlab/ui": fileURLToPath(
new URL("../../packages/ui/src/index.ts", import.meta.url),
),
$lib: fileURLToPath(new URL("./src/lib", import.meta.url)),
},
},
server: createDevServerConfig(),
test: {
exclude: [...configDefaults.exclude, "tests/e2e/**"],
},
});