refactor(model): extract dashboard model package
This commit is contained in:
parent
22f50d64f9
commit
7a6ad8ab0e
35 changed files with 164 additions and 57 deletions
148
apps/web/src/lib/dashboard-seed/dimensionlab.test.ts
Normal file
148
apps/web/src/lib/dashboard-seed/dimensionlab.test.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
dimensionLabDashboardFixture,
|
||||
} from "./dimensionlab";
|
||||
import type {
|
||||
DashboardDocument,
|
||||
DatasourceReference,
|
||||
ServiceEntry,
|
||||
TelemetryCard,
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
|
||||
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",
|
||||
]);
|
||||
681
apps/web/src/lib/dashboard-seed/dimensionlab.ts
Normal file
681
apps/web/src/lib/dashboard-seed/dimensionlab.ts
Normal file
|
|
@ -0,0 +1,681 @@
|
|||
import {
|
||||
DASHBOARD_SCHEMA_VERSION,
|
||||
type DashboardDocument,
|
||||
type DatasourceReference,
|
||||
type MetricValue,
|
||||
type ServiceEntry,
|
||||
type ServiceGroup,
|
||||
type Severity,
|
||||
type TelemetryCard,
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
|
||||
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}`);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue