diff --git a/README.md b/README.md index c49bbd4..9981182 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,6 @@ Stored dashboard documents pass through a version migration boundary before reads or writes; the MVP supports `dashboard.v1` and fails unsupported versions with an explicit migration error. -## Seed Data - -The initial Dimension Lab dashboard lives in -`src/lib/model/fixtures/dimensionlab.ts` as validated model data. It includes -the first-screen telemetry, service groups, status strip, weather module, -Iconify icon identifiers, links, and datasource references. Values that are not -live yet are labeled as fallback values in the data so later datasource adapters -can replace them without changing presentation components. - ## Runtime Shape The SvelteKit build uses the Node adapter, and the current persistence runtime diff --git a/src/lib/model/fixtures/dimensionlab.test.ts b/src/lib/model/fixtures/dimensionlab.test.ts deleted file mode 100644 index ae13cbf..0000000 --- a/src/lib/model/fixtures/dimensionlab.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -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:thermometer", - "mdi:web", - "mdi:weather-sunny", - "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", -]); diff --git a/src/lib/model/fixtures/dimensionlab.ts b/src/lib/model/fixtures/dimensionlab.ts index 9890fef..be23b02 100644 --- a/src/lib/model/fixtures/dimensionlab.ts +++ b/src/lib/model/fixtures/dimensionlab.ts @@ -1,48 +1,14 @@ 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: "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.", + subtitle: "Capacity, noise & top consumers", + description: "Dimension Lab operational dashboard seed data.", timezone: "Europe/Amsterdam", refreshIntervalSeconds: 15, }, @@ -68,528 +34,130 @@ export const dimensionLabDashboardFixture: DashboardDocument = { ], serviceGroups: ["essentials", "monitoring", "ai-automation", "systems", "runtime-health"], statusStrips: ["footer-status"], - modules: ["weather-amsterdam", "runtime-health-summary"], + modules: ["weather"], }, 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( - '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], - }), + 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"), ], serviceGroups: [ group("essentials", "Essentials", [ - 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:amazonwebservices", - href: "https://dimensionlab.awsapps.com/start", - datasource: httpStatus("https://dimensionlab.awsapps.com/start"), - }), + 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"), ]), group("monitoring", "Monitoring", [ - 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"), - }), + 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"), ]), group("ai-automation", "AI & Automation", [ - 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: "mdi:web", - 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"), - }), + 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"), ]), group("systems", "Systems", [ - 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"), - }), + 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"), ]), 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: 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", - }), + 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"), ]), ], 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: "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" }, ], }, ], modules: [ { - id: "weather-amsterdam", + id: "weather", kind: "weather", title: "Amsterdam", value: "28.3 C", - detail: "fallback - weather adapter pending", + detail: "Clear", 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"), + datasource: { type: "placeholder", reason: "weather adapter pending" }, }, ], }; -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, - }; -} +type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number"; +type Severity = "neutral" | "ok" | "warning" | "danger" | "stale" | "unavailable"; -function metricValue( +function metric( + id: string, + label: string, 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; - + detail: string, + severity: Severity, +) { return { - ...entry, - link: { - href: seed.href, - label: `Open ${seed.label}`, - external: true, - }, + id, + label, + value: { kind, value }, + detail, + severity, + datasource: { type: "placeholder" as const, reason: "live datasource pending" }, + sparkline: [10, 18, 15, 28, 24, 32], }; } -function group(id: string, title: string, services: ServiceEntry[]): ServiceGroup { +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[]) { return { id, title, - layout: "list", + layout: "list" as const, 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, - }; -} diff --git a/src/lib/server/dashboard.ts b/src/lib/server/dashboard.ts index 56380a4..244e97b 100644 --- a/src/lib/server/dashboard.ts +++ b/src/lib/server/dashboard.ts @@ -17,8 +17,8 @@ export interface PlaceholderDashboard { export function loadPlaceholderDashboard(): PlaceholderDashboard { return { - title: "Dashboard Runtime", - subtitle: "Runtime scaffold", + title: "System Overview", + subtitle: "Dashboard 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: "initial-seed", - message: "load initial dashboard document", + actor: "dimensionlab-seed", + message: "load initial dashboard seed", }); const active = dashboardStore.getActiveDashboard(); const document = active?.document || seeded.document;