feat: add Dimension Lab seed model
This commit is contained in:
parent
25996c38a9
commit
077edbd550
4 changed files with 622 additions and 87 deletions
94
src/lib/model/fixtures/dimensionlab.test.ts
Normal file
94
src/lib/model/fixtures/dimensionlab.test.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
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", () => {
|
||||
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(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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("labels fallback values and unresolved adapters explicitly", () => {
|
||||
const placeholders = collectDatasources(dimensionLabDashboardFixture).filter(
|
||||
(datasource) => datasource.type === "placeholder",
|
||||
);
|
||||
const details = [
|
||||
...dimensionLabDashboardFixture.telemetry.map((card) => card.detail || ""),
|
||||
...(dimensionLabDashboardFixture.modules || []).map((module) => module.detail || ""),
|
||||
].join("\n").toLowerCase();
|
||||
|
||||
expect(details).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];
|
||||
}
|
||||
|
|
@ -1,14 +1,48 @@
|
|||
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;
|
||||
}
|
||||
|
||||
export const dimensionLabDashboardFixture: DashboardDocument = {
|
||||
schemaVersion: DASHBOARD_SCHEMA_VERSION,
|
||||
metadata: {
|
||||
title: "System Overview",
|
||||
subtitle: "Capacity, noise & top consumers",
|
||||
description: "Dimension Lab operational dashboard seed data.",
|
||||
subtitle: "Dimension Lab home infra, GPU, and automation surface",
|
||||
description:
|
||||
"Initial Dimension Lab dashboard seed data. Runtime values are fallback values until datasource adapters are enabled.",
|
||||
timezone: "Europe/Amsterdam",
|
||||
refreshIntervalSeconds: 15,
|
||||
},
|
||||
|
|
@ -34,130 +68,528 @@ export const dimensionLabDashboardFixture: DashboardDocument = {
|
|||
],
|
||||
serviceGroups: ["essentials", "monitoring", "ai-automation", "systems", "runtime-health"],
|
||||
statusStrips: ["footer-status"],
|
||||
modules: ["weather"],
|
||||
modules: ["weather-amsterdam", "runtime-health-summary"],
|
||||
},
|
||||
telemetry: [
|
||||
metric("infra-ram", "Infra RAM", "percent", 19, "linux-infra", "ok"),
|
||||
metric("gpu-host-ram", "GPU Host RAM", "percent", 17, "linux", "ok"),
|
||||
metric("network-ram", "Network RAM", "percent", 5, "network-core", "ok"),
|
||||
metric("user-disk-peak", "User Disk Peak", "percent", 29, "linux /home", "ok"),
|
||||
metric("system-disk-peak", "System Disk Peak", "percent", 49, "linux /boot", "ok"),
|
||||
metric("peak-cpu-busy", "Peak CPU Busy", "percent", 3, "linux-infra", "ok"),
|
||||
metric("top-cpu-container", "Top CPU Container", "percent", 7.9, "container placeholder", "ok"),
|
||||
metric("top-ram-container", "Top RAM Container", "bytes", 5.2 * 1024 ** 3, "container placeholder", "danger"),
|
||||
metric("gpu-3060-load", "3060 GPU Load", "percent", 0, "RTX 3060", "ok"),
|
||||
metric("gpu-3060-vram", "3060 VRAM", "percent", 0, "RTX 3060", "ok"),
|
||||
metric("gpu-3060-temp", "3060 Temp", "temperature", 54, "RTX 3060", "ok"),
|
||||
metric("gpu-3060-fan", "3060 Fan Spin", "percent", 0, "RTX 3060", "ok"),
|
||||
metric("gpu-3090-load", "3090 GPU Load", "percent", 0, "RTX 3090", "ok"),
|
||||
metric("gpu-3090-vram", "3090 VRAM", "percent", 74, "RTX 3090", "warning"),
|
||||
metric("gpu-3090-temp", "3090 Temp", "temperature", 50, "RTX 3090", "ok"),
|
||||
metric("gpu-3090-fan", "3090 Fan Spin", "percent", 0, "RTX 3090", "ok"),
|
||||
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(
|
||||
'100 * (1 - node_memory_MemAvailable_bytes{instance="linux-infra"} / node_memory_MemTotal_bytes{instance="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(
|
||||
'100 * (1 - node_memory_MemAvailable_bytes{instance="linux-gpu"} / node_memory_MemTotal_bytes{instance="linux-gpu"})',
|
||||
),
|
||||
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(
|
||||
'100 * (1 - node_memory_MemAvailable_bytes{instance="network-core"} / node_memory_MemTotal_bytes{instance="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(
|
||||
'100 * (1 - node_filesystem_avail_bytes{mountpoint="/home"} / node_filesystem_size_bytes{mountpoint="/home"})',
|
||||
),
|
||||
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(
|
||||
'100 * (1 - node_filesystem_avail_bytes{mountpoint="/boot"} / node_filesystem_size_bytes{mountpoint="/boot"})',
|
||||
),
|
||||
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(
|
||||
'100 - (avg by(instance) (rate(node_cpu_seconds_total{instance="linux-infra",mode="idle"}[5m])) * 100)',
|
||||
),
|
||||
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: placeholder(
|
||||
"fallback top-container CPU query pending container label normalization",
|
||||
),
|
||||
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: placeholder(
|
||||
"fallback top-container memory query pending container label normalization",
|
||||
),
|
||||
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('DCGM_FI_DEV_GPU_UTIL{gpu="rtx-3060"}'),
|
||||
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(
|
||||
'100 * DCGM_FI_DEV_FB_USED{gpu="rtx-3060"} / (DCGM_FI_DEV_FB_USED{gpu="rtx-3060"} + DCGM_FI_DEV_FB_FREE{gpu="rtx-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('DCGM_FI_DEV_GPU_TEMP{gpu="rtx-3060"}'),
|
||||
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('DCGM_FI_DEV_FAN_SPEED{gpu="rtx-3060"}'),
|
||||
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('DCGM_FI_DEV_GPU_UTIL{gpu="rtx-3090"}'),
|
||||
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(
|
||||
'100 * DCGM_FI_DEV_FB_USED{gpu="rtx-3090"} / (DCGM_FI_DEV_FB_USED{gpu="rtx-3090"} + DCGM_FI_DEV_FB_FREE{gpu="rtx-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('DCGM_FI_DEV_GPU_TEMP{gpu="rtx-3090"}'),
|
||||
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('DCGM_FI_DEV_FAN_SPEED{gpu="rtx-3090"}'),
|
||||
sparkline: [0, 0, 0, 0, 0, 0],
|
||||
}),
|
||||
],
|
||||
serviceGroups: [
|
||||
group("essentials", "Essentials", [
|
||||
service("vaultwarden", "Vaultwarden", "Password manager", "simple-icons:vaultwarden", "https://vault.dimensionlab.net"),
|
||||
service("forgejo", "Forgejo", "Git repositories", "simple-icons:forgejo", "https://git.dimensionlab.net"),
|
||||
service("wiki", "Wiki", "Internal documentation", "simple-icons:wikidotjs", "https://wiki.dimensionlab.net"),
|
||||
service("aws-start", "AWS Start", "AWS access portal", "simple-icons:amazonaws", "https://dimensionlab.awsapps.com/start"),
|
||||
service({
|
||||
id: "vaultwarden",
|
||||
label: "Vaultwarden",
|
||||
description: "Password manager",
|
||||
icon: "simple-icons:vaultwarden",
|
||||
href: "https://vault.dimensionlab.net",
|
||||
datasource: httpStatus("https://vault.dimensionlab.net/alive"),
|
||||
}),
|
||||
service({
|
||||
id: "forgejo",
|
||||
label: "Forgejo",
|
||||
description: "Git repositories",
|
||||
icon: "simple-icons:forgejo",
|
||||
href: "https://git.dimensionlab.net",
|
||||
datasource: httpStatus("https://git.dimensionlab.net/api/healthz"),
|
||||
}),
|
||||
service({
|
||||
id: "wiki",
|
||||
label: "Wiki",
|
||||
description: "Internal documentation",
|
||||
icon: "simple-icons:wikidotjs",
|
||||
href: "https://wiki.dimensionlab.net",
|
||||
datasource: httpStatus("https://wiki.dimensionlab.net"),
|
||||
}),
|
||||
service({
|
||||
id: "aws-start",
|
||||
label: "AWS Start",
|
||||
description: "AWS access portal",
|
||||
icon: "simple-icons:amazonaws",
|
||||
href: "https://dimensionlab.awsapps.com/start",
|
||||
datasource: httpStatus("https://dimensionlab.awsapps.com/start"),
|
||||
}),
|
||||
]),
|
||||
group("monitoring", "Monitoring", [
|
||||
service("grafana", "Grafana", "Capacity and noise dashboard", "simple-icons:grafana", "https://grafana.dimensionlab.net"),
|
||||
service("uptime-kuma", "Uptime Kuma", "Service uptime checks", "simple-icons:uptimekuma", "https://uptime.dimensionlab.net"),
|
||||
service("prometheus", "Prometheus", "Metrics database", "simple-icons:prometheus", "https://prometheus.dimensionlab.net"),
|
||||
service("backrest", "Backrest", "Restic backup manager", "mdi:backup-restore", "https://backups.dimensionlab.net"),
|
||||
service({
|
||||
id: "grafana",
|
||||
label: "Grafana",
|
||||
description: "Capacity and noise dashboard",
|
||||
icon: "simple-icons:grafana",
|
||||
href: "https://grafana.dimensionlab.net",
|
||||
datasource: httpStatus("https://grafana.dimensionlab.net/api/health"),
|
||||
}),
|
||||
service({
|
||||
id: "uptime-kuma",
|
||||
label: "Uptime Kuma",
|
||||
description: "Service uptime checks",
|
||||
icon: "simple-icons:uptimekuma",
|
||||
href: "https://uptime.dimensionlab.net",
|
||||
datasource: httpStatus("https://uptime.dimensionlab.net/status/dimensionlab"),
|
||||
}),
|
||||
service({
|
||||
id: "prometheus",
|
||||
label: "Prometheus",
|
||||
description: "Metrics database",
|
||||
icon: "simple-icons:prometheus",
|
||||
href: "https://prometheus.dimensionlab.net",
|
||||
datasource: httpStatus("https://prometheus.dimensionlab.net/-/ready"),
|
||||
}),
|
||||
service({
|
||||
id: "backrest",
|
||||
label: "Backrest",
|
||||
description: "Restic backup manager",
|
||||
icon: "mdi:backup-restore",
|
||||
href: "https://backups.dimensionlab.net",
|
||||
datasource: httpStatus("https://backups.dimensionlab.net"),
|
||||
}),
|
||||
]),
|
||||
group("ai-automation", "AI & Automation", [
|
||||
service("n8n", "n8n", "Workflow automation", "simple-icons:n8n", "https://workflows.dimensionlab.net"),
|
||||
service("open-webui", "Open WebUI", "Chat and model interface", "simple-icons:openwebui", "https://webui.dimensionlab.net"),
|
||||
service("comfyui", "ComfyUI", "Image generation workflows", "mdi:image-edit-outline", "https://comfy.dimensionlab.net"),
|
||||
service("models", "Models", "Local model management", "mdi:brain", "https://models.dimensionlab.net"),
|
||||
service({
|
||||
id: "n8n",
|
||||
label: "n8n",
|
||||
description: "Workflow automation",
|
||||
icon: "simple-icons:n8n",
|
||||
href: "https://workflows.dimensionlab.net",
|
||||
datasource: httpStatus("https://workflows.dimensionlab.net/healthz"),
|
||||
}),
|
||||
service({
|
||||
id: "open-webui",
|
||||
label: "Open WebUI",
|
||||
description: "Chat and model interface",
|
||||
icon: "simple-icons:openwebui",
|
||||
href: "https://webui.dimensionlab.net",
|
||||
datasource: httpStatus("https://webui.dimensionlab.net/health"),
|
||||
}),
|
||||
service({
|
||||
id: "comfyui",
|
||||
label: "ComfyUI",
|
||||
description: "Image generation workflows",
|
||||
icon: "mdi:image-edit-outline",
|
||||
href: "https://comfy.dimensionlab.net",
|
||||
datasource: httpStatus("https://comfy.dimensionlab.net"),
|
||||
}),
|
||||
service({
|
||||
id: "models",
|
||||
label: "Models",
|
||||
description: "Local model management",
|
||||
icon: "mdi:brain",
|
||||
href: "https://models.dimensionlab.net",
|
||||
datasource: httpStatus("https://models.dimensionlab.net/api/tags"),
|
||||
}),
|
||||
]),
|
||||
group("systems", "Systems", [
|
||||
service("adminer", "Adminer", "PostgreSQL database browser", "simple-icons:adminer", "https://db.dimensionlab.net"),
|
||||
service("assistant", "Assistant", "Personal AI agent gateway", "mdi:robot-outline", "https://assistant.dimensionlab.net"),
|
||||
service("suna", "Suna", "AI command center", "mdi:account-hard-hat-outline", "https://suna.dimensionlab.net"),
|
||||
service("cockpit-infra", "Cockpit Infra", "linux-infra server console", "simple-icons:cockpit", "https://infra-cockpit.dimensionlab.net"),
|
||||
service({
|
||||
id: "adminer",
|
||||
label: "Adminer",
|
||||
description: "PostgreSQL database browser",
|
||||
icon: "simple-icons:adminer",
|
||||
href: "https://db.dimensionlab.net",
|
||||
datasource: httpStatus("https://db.dimensionlab.net"),
|
||||
}),
|
||||
service({
|
||||
id: "assistant",
|
||||
label: "Assistant",
|
||||
description: "Personal AI agent gateway",
|
||||
icon: "mdi:robot-outline",
|
||||
href: "https://assistant.dimensionlab.net",
|
||||
datasource: httpStatus("https://assistant.dimensionlab.net/health"),
|
||||
}),
|
||||
service({
|
||||
id: "suna",
|
||||
label: "Suna",
|
||||
description: "AI command center",
|
||||
icon: "mdi:account-hard-hat-outline",
|
||||
href: "https://suna.dimensionlab.net",
|
||||
datasource: httpStatus("https://suna.dimensionlab.net"),
|
||||
}),
|
||||
service({
|
||||
id: "cockpit-infra",
|
||||
label: "Cockpit Infra",
|
||||
description: "linux-infra server console",
|
||||
icon: "simple-icons:cockpit",
|
||||
href: "https://infra-cockpit.dimensionlab.net",
|
||||
datasource: httpStatus("https://infra-cockpit.dimensionlab.net"),
|
||||
}),
|
||||
]),
|
||||
group("runtime-health", "Runtime Health", [
|
||||
service("forgejo-ssh-relay", "Forgejo SSH Relay", "Public Git SSH relay", "simple-icons:forgejo", "https://uptime.dimensionlab.net/status/dimensionlab"),
|
||||
service("postgresql", "PostgreSQL", "Shared application database", "simple-icons:postgresql"),
|
||||
service("ollama-api", "Ollama API", "Local model API", "simple-icons:ollama"),
|
||||
service("node-exporter", "Node Exporter", "Host metrics exporter", "simple-icons:prometheus"),
|
||||
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: custom("tcp:git.dimensionlab.net:22"),
|
||||
detail: "fallback - uptime monitor pending",
|
||||
}),
|
||||
service({
|
||||
id: "postgresql",
|
||||
label: "PostgreSQL",
|
||||
description: "Shared application database",
|
||||
icon: "simple-icons:postgresql",
|
||||
datasource: prometheus('pg_up{cluster="dimensionlab"}'),
|
||||
detail: "fallback - postgres exporter pending",
|
||||
}),
|
||||
service({
|
||||
id: "ollama-api",
|
||||
label: "Ollama API",
|
||||
description: "Local model API",
|
||||
icon: "simple-icons:ollama",
|
||||
datasource: httpStatus("http://linux-gpu:11434/api/tags"),
|
||||
detail: "fallback - internal health check pending",
|
||||
}),
|
||||
service({
|
||||
id: "node-exporter",
|
||||
label: "Node Exporter",
|
||||
description: "Host metrics exporter",
|
||||
icon: "simple-icons:prometheus",
|
||||
datasource: prometheus('up{job="node-exporter"}'),
|
||||
detail: "fallback - exporter health from Prometheus",
|
||||
}),
|
||||
]),
|
||||
],
|
||||
statusStrips: [
|
||||
{
|
||||
id: "footer-status",
|
||||
items: [
|
||||
{ id: "system-status", label: "System Status", value: "All systems operational", severity: "ok" },
|
||||
{ id: "last-sync", label: "Last Sync", value: "2 minutes ago", severity: "stale" },
|
||||
{ id: "uptime", label: "Uptime", value: "28d 14h 32m", severity: "ok" },
|
||||
{ id: "load-avg", label: "Load Avg", value: "0.47 0.53 0.59", severity: "neutral" },
|
||||
{ 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" },
|
||||
],
|
||||
},
|
||||
],
|
||||
modules: [
|
||||
{
|
||||
id: "weather",
|
||||
id: "weather-amsterdam",
|
||||
kind: "weather",
|
||||
title: "Amsterdam",
|
||||
value: "28.3 C",
|
||||
detail: "Clear",
|
||||
detail: "fallback - weather adapter pending",
|
||||
icon: "mdi:weather-sunny",
|
||||
severity: "ok",
|
||||
datasource: { type: "placeholder", reason: "weather adapter pending" },
|
||||
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"),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number";
|
||||
type Severity = "neutral" | "ok" | "warning" | "danger" | "stale" | "unavailable";
|
||||
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 metric(
|
||||
id: string,
|
||||
label: string,
|
||||
function metricValue(
|
||||
kind: NumericValueKind,
|
||||
value: number,
|
||||
detail: string,
|
||||
severity: Severity,
|
||||
) {
|
||||
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 {
|
||||
id,
|
||||
label,
|
||||
value: { kind, value },
|
||||
detail,
|
||||
severity,
|
||||
datasource: { type: "placeholder" as const, reason: "live datasource pending" },
|
||||
sparkline: [10, 18, 15, 28, 24, 32],
|
||||
...entry,
|
||||
link: {
|
||||
href: seed.href,
|
||||
label: `Open ${seed.label}`,
|
||||
external: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function service(
|
||||
id: string,
|
||||
label: string,
|
||||
description: string,
|
||||
icon: string,
|
||||
href?: string,
|
||||
) {
|
||||
const entry = {
|
||||
id,
|
||||
label,
|
||||
description,
|
||||
icon,
|
||||
severity: "ok" as const,
|
||||
detail: "ready",
|
||||
datasource: { type: "placeholder" as const, reason: "service health adapter pending" },
|
||||
};
|
||||
|
||||
return href ? { ...entry, link: { href, external: true } } : entry;
|
||||
}
|
||||
|
||||
function group(id: string, title: string, services: ReturnType<typeof service>[]) {
|
||||
function group(id: string, title: string, services: ServiceEntry[]): ServiceGroup {
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
layout: "list" as const,
|
||||
layout: "list",
|
||||
services,
|
||||
};
|
||||
}
|
||||
|
||||
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 custom(reference: string): DatasourceReference {
|
||||
return {
|
||||
type: "external",
|
||||
adapter: "custom",
|
||||
reference,
|
||||
};
|
||||
}
|
||||
|
||||
function placeholder(reason: string): DatasourceReference {
|
||||
return {
|
||||
type: "placeholder",
|
||||
reason,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export interface PlaceholderDashboard {
|
|||
|
||||
export function loadPlaceholderDashboard(): PlaceholderDashboard {
|
||||
return {
|
||||
title: "System Overview",
|
||||
subtitle: "Dashboard runtime scaffold",
|
||||
title: "Dashboard Runtime",
|
||||
subtitle: "Runtime scaffold",
|
||||
status: "building",
|
||||
message:
|
||||
"SvelteKit is running. Later issues will replace this placeholder with the validated dashboard model.",
|
||||
|
|
@ -30,8 +30,8 @@ export function loadDashboardRuntime(store?: DashboardStore): PlaceholderDashboa
|
|||
|
||||
try {
|
||||
const seeded = dashboardStore.seedDashboardIfEmpty(dimensionLabDashboardFixture, {
|
||||
actor: "dimensionlab-seed",
|
||||
message: "load initial dashboard seed",
|
||||
actor: "initial-seed",
|
||||
message: "load initial dashboard document",
|
||||
});
|
||||
const active = dashboardStore.getActiveDashboard();
|
||||
const document = active?.document || seeded.document;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue