refactor(model): extract dashboard model package

This commit is contained in:
vince 2026-06-20 06:26:19 +02:00
parent 22f50d64f9
commit 7a6ad8ab0e
35 changed files with 164 additions and 57 deletions

View 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",
]);

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 "@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}`);
}