diff --git a/packages/db/migrations/seed.ts b/packages/db/migrations/seed.ts index 888339bea..d491b6092 100644 --- a/packages/db/migrations/seed.ts +++ b/packages/db/migrations/seed.ts @@ -412,8 +412,9 @@ const buildDemoWidgets = (appIds: string[]): DemoWidget[] => [ // Row 9: beszelSystemGrid + beszelAlerts = 12 { kind: "beszelSystemGrid", width: 8, height: 3, needsIntegration: true }, { kind: "beszelAlerts", width: 4, height: 3, needsIntegration: true }, - // Row 10: beszelSystemTable = 12 - { kind: "beszelSystemTable", width: 12, height: 3, needsIntegration: true }, + // Row 10: beszelSystemTable + beszelSystemStats = 12 + { kind: "beszelSystemTable", width: 6, height: 3, needsIntegration: true }, + { kind: "beszelSystemStats", width: 6, height: 4, needsIntegration: true }, // Row 11: notebook + dockerContainers + weather = 12 { kind: "notebook", width: 4, height: 4, needsIntegration: false }, { kind: "dockerContainers", width: 6, height: 2, needsIntegration: false }, diff --git a/packages/integrations/src/mock/data/beszel.ts b/packages/integrations/src/mock/data/beszel.ts index 8ebbc3402..3cd36c460 100644 --- a/packages/integrations/src/mock/data/beszel.ts +++ b/packages/integrations/src/mock/data/beszel.ts @@ -9,109 +9,133 @@ import type { BeszelSystemStatsRecord, } from "../../beszel/beszel-types"; -const mockSystems: { name: string; host: string; os: string; cpu: string; cores: number; mem: number }[] = [ - { - name: "web-server", - host: "192.168.1.10", - os: "Ubuntu 24.04", - cpu: "AMD Ryzen 9 7950X", - cores: 16, - mem: 64 * 1024 * 1024 * 1024, - }, - { - name: "nas-01", - host: "192.168.1.20", - os: "Debian 12", - cpu: "Intel Xeon E-2388G", - cores: 8, - mem: 32 * 1024 * 1024 * 1024, - }, - { - name: "pi-cluster-1", - host: "192.168.1.30", - os: "Raspberry Pi OS", - cpu: "ARM Cortex-A76", - cores: 4, - mem: 8 * 1024 * 1024 * 1024, - }, - { - name: "media-server", - host: "192.168.1.40", - os: "Proxmox VE 8.2", - cpu: "Intel i7-13700K", - cores: 16, - mem: 128 * 1024 * 1024 * 1024, - }, - { - name: "backup-node", - host: "192.168.1.50", - os: "TrueNAS SCALE", - cpu: "Intel Xeon E5-2680", - cores: 12, - mem: 48 * 1024 * 1024 * 1024, - }, +const GB = 1024 * 1024 * 1024; +const MB = 1024 * 1024; +const KB = 1024; + +interface MockSystemDef { + name: string; + host: string; + os: string; + cpu: string; + cores: number; + mem: number; + arch: string; + baseCpu: number; + baseMem: number; + baseDisk: number; + containers: number[]; +} + +const mockSystems: MockSystemDef[] = [ + { name: "web-server", host: "192.168.1.10", os: "Ubuntu 24.04", cpu: "AMD Ryzen 9 7950X", cores: 16, mem: 64 * GB, arch: "amd64", baseCpu: 35, baseMem: 55, baseDisk: 42, containers: [0, 1, 2, 3, 5] }, + { name: "nas-01", host: "192.168.1.20", os: "Debian 12", cpu: "Intel Xeon E-2388G", cores: 8, mem: 32 * GB, arch: "amd64", baseCpu: 12, baseMem: 70, baseDisk: 68, containers: [1, 2, 6] }, + { name: "pi-cluster-1", host: "192.168.1.30", os: "Raspberry Pi OS", cpu: "ARM Cortex-A76", cores: 4, mem: 8 * GB, arch: "aarch64", baseCpu: 45, baseMem: 82, baseDisk: 35, containers: [0, 2, 7] }, + { name: "media-server", host: "192.168.1.40", os: "Proxmox VE 8.2", cpu: "Intel i7-13700K", cores: 16, mem: 128 * GB, arch: "amd64", baseCpu: 22, baseMem: 48, baseDisk: 55, containers: [0, 1, 2, 3, 4, 5, 6, 7] }, + { name: "backup-node", host: "192.168.1.50", os: "TrueNAS SCALE", cpu: "Intel Xeon E5-2680", cores: 12, mem: 48 * GB, arch: "amd64", baseCpu: 8, baseMem: 30, baseDisk: 78, containers: [1, 6] }, ]; -const containerNames = ["nginx", "postgres", "redis", "grafana", "prometheus", "traefik", "minio", "gitea"]; +const containerProfiles: { name: string; cpuBase: number; memMB: number; netKB: number }[] = [ + { name: "nginx", cpuBase: 2, memMB: 120, netKB: 800 }, + { name: "postgres", cpuBase: 8, memMB: 450, netKB: 200 }, + { name: "redis", cpuBase: 3, memMB: 180, netKB: 500 }, + { name: "grafana", cpuBase: 5, memMB: 280, netKB: 150 }, + { name: "prometheus", cpuBase: 6, memMB: 350, netKB: 300 }, + { name: "traefik", cpuBase: 4, memMB: 90, netKB: 1200 }, + { name: "minio", cpuBase: 3, memMB: 200, netKB: 600 }, + { name: "gitea", cpuBase: 4, memMB: 250, netKB: 100 }, +]; const rand = (min: number, max: number) => min + Math.random() * (max - min); const randInt = (min: number, max: number) => Math.floor(rand(min, max)); +const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v)); -const BYTES_GB = 1024 * 1024 * 1024; -const BYTES_MB = 1024 * 1024; - -function generateSystemStats(baseLoad: number): BeszelSystemStats { - const cpuJitter = rand(-5, 5); - const memTotal = rand(8, 128) * BYTES_GB; - const memUsed = memTotal * rand(0.3, 0.85); - const diskTotal = rand(200, 2000) * BYTES_GB; - const diskUsed = diskTotal * rand(0.2, 0.7); - - return { - cpu: Math.max(0, Math.min(100, baseLoad + cpuJitter)), - la: [rand(0.1, 4), rand(0.1, 3), rand(0.1, 2)], - m: memTotal, - mu: memUsed, - mp: (memUsed / memTotal) * 100, - mb: rand(0.5, 4) * BYTES_GB, - s: 4 * BYTES_GB, - su: rand(0, 2) * BYTES_GB, - d: diskTotal, - du: diskUsed, - dp: (diskUsed / diskTotal) * 100, - dr: rand(0, 50) * BYTES_MB, - dw: rand(0, 30) * BYTES_MB, - ns: rand(100, 5000) * 1024, - nr: rand(100, 8000) * 1024, - b: [rand(50, 3000) * 1024, rand(100, 5000) * 1024], - }; +function smoothWalk(base: number, amplitude: number, steps: number, lo: number, hi: number): number[] { + const values: number[] = []; + let current = base + rand(-amplitude * 0.3, amplitude * 0.3); + for (let i = 0; i < steps; i++) { + const spike = Math.random() < 0.08 ? rand(-amplitude * 2, amplitude * 3) : 0; + current += rand(-amplitude, amplitude) + spike; + current = clamp(current, lo, hi); + values.push(current); + } + return values; } -function generateContainerStats(names: string[]): BeszelContainerStats[] { - return names.map((n) => ({ - n, - c: rand(0, 25), - m: rand(50, 500) * BYTES_MB, - b: [rand(0, 500) * 1024, rand(0, 800) * 1024], +function resolveSystem(systemId: string): MockSystemDef { + const idx = Number(systemId.replace("mock-sys-", "")) - 1; + return mockSystems[idx] ?? mockSystems[0] ?? mockSystems.find(Boolean) as MockSystemDef; +} + +const typeToMinutes: Record = { + "1m": 1, "10m": 10, "20m": 20, "120m": 120, "480m": 480, +}; + +function generateSystemStatsSeries(count: number, sys: MockSystemDef): BeszelSystemStats[] { + const cpuWalk = smoothWalk(sys.baseCpu, 3, count, 1, 98); + const memPctWalk = smoothWalk(sys.baseMem, 1.5, count, 10, 95); + const diskPctWalk = smoothWalk(sys.baseDisk, 0.2, count, 5, 95); + const diskReadWalk = smoothWalk(5, 3, count, 0, 80); + const diskWriteWalk = smoothWalk(3, 2, count, 0, 50); + const netSendWalk = smoothWalk(500, 200, count, 10, 8000); + const netRecvWalk = smoothWalk(800, 300, count, 10, 12000); + + const memTotal = sys.mem; + const diskTotal = rand(200, 2000) * GB; + + return cpuWalk.map((cpu, i) => { + const memPct = memPctWalk[i] ?? sys.baseMem; + const diskPct = diskPctWalk[i] ?? sys.baseDisk; + return { + cpu, + la: [cpu / 20 + rand(0, 0.5), cpu / 25 + rand(0, 0.3), cpu / 30 + rand(0, 0.2)] as [number, number, number], + m: memTotal, + mu: memTotal * memPct / 100, + mp: memPct, + mb: rand(0.5, 4) * GB, + s: 4 * GB, + su: rand(0, 2) * GB, + d: diskTotal, + du: diskTotal * diskPct / 100, + dp: diskPct, + dr: (diskReadWalk[i] ?? 5) * MB, + dw: (diskWriteWalk[i] ?? 3) * MB, + ns: (netSendWalk[i] ?? 500) * KB, + nr: (netRecvWalk[i] ?? 800) * KB, + b: [(netSendWalk[i] ?? 500) * KB * 0.7, (netRecvWalk[i] ?? 800) * KB * 0.7] as [number, number], + }; + }); +} + +function generateContainerStatsSeries(count: number, profiles: typeof containerProfiles): BeszelContainerStats[][] { + const walks = profiles.map((p) => ({ + profile: p, + cpuWalk: smoothWalk(p.cpuBase, 1.5, count, 0, 50), + memWalk: smoothWalk(p.memMB, p.memMB * 0.1, count, p.memMB * 0.5, p.memMB * 2), + netWalk: smoothWalk(p.netKB, p.netKB * 0.3, count, 0, p.netKB * 4), })); + + return Array.from({ length: count }, (_, i) => + walks.map(({ profile, cpuWalk, memWalk, netWalk }) => ({ + n: profile.name, + c: cpuWalk[i] ?? profile.cpuBase, + m: (memWalk[i] ?? profile.memMB) * MB, + b: [(netWalk[i] ?? profile.netKB) * KB * 0.4, (netWalk[i] ?? profile.netKB) * KB * 0.6] as [number, number], + })), + ); } -function generateTimeSeries( - count: number, - intervalMinutes: number, - generator: () => T, -): (T & { id: string; system: string; type: string; created: string; updated: string })[] { +function buildTimeSeries(systemId: string, count: number, intervalMinutes: number, type: string, dataPoints: T[]): (T & { id: string; system: string; type: string; created: string; updated: string })[] { const now = Date.now(); - return Array.from({ length: count }, (_, i) => ({ - id: `mock-stat-${i}`, - system: "mock-sys-1", - type: `${intervalMinutes}m`, - created: new Date(now - (count - i) * intervalMinutes * 60_000).toISOString(), - updated: new Date(now - (count - i) * intervalMinutes * 60_000).toISOString(), - ...generator(), - })); + const items = dataPoints.slice(0, count).map((data, i) => { + const ts = new Date(now - (count - i) * intervalMinutes * 60_000).toISOString(); + return { id: `mock-stat-${i}`, system: systemId, type, created: ts, updated: ts, ...data }; + }); + return items.reverse(); } +const statusByIndex: Record = { 2: "paused", 4: "down" }; + export class BeszelMockService { public async getSystemsAsync(): Promise { return mockSystems.map((sys, i) => ({ @@ -119,23 +143,23 @@ export class BeszelMockService { name: sys.name, host: sys.host, port: "45876", - status: i === 2 ? ("paused" as const) : ("up" as const), + status: statusByIndex[i] ?? "up", info: { h: sys.name, - cpu: rand(5, 85), + cpu: clamp(sys.baseCpu + rand(-5, 10), 0, 100), c: sys.cores, ct: sys.cores * 2, m: sys.cpu, - la: [rand(0.1, 4), rand(0.1, 3), rand(0.1, 2)] as [number, number, number], + la: [sys.baseCpu / 20, sys.baseCpu / 25, sys.baseCpu / 30] as [number, number, number], o: sys.os, - u: randInt(3600, 2_000_000), - mp: rand(20, 85), - dp: rand(15, 70), - bb: rand(100, 5000) * 1024, + u: randInt(86400, 2_000_000), + mp: clamp(sys.baseMem + rand(-5, 5), 0, 100), + dp: clamp(sys.baseDisk + rand(-2, 2), 0, 100), + bb: rand(200, 3000) * KB, v: "0.8.2", - g: i === 3 ? rand(10, 60) : 0, - dt: rand(30, 65), - sv: [randInt(5, 30), randInt(30, 50)] as [number, number], + g: i === 3 ? rand(15, 45) : 0, + dt: rand(32, 58), + sv: [randInt(8, 25), randInt(25, 40)] as [number, number], }, users: [], created: new Date().toISOString(), @@ -144,8 +168,7 @@ export class BeszelMockService { } public async getSystemDetailsAsync(systemId: string): Promise { - const idx = Number(systemId.replace("mock-sys-", "")) - 1; - const sys = mockSystems[idx] ?? mockSystems[0]!; + const sys = resolveSystem(systemId); return { id: systemId, system: systemId, @@ -156,72 +179,46 @@ export class BeszelMockService { cpu: sys.cpu, os: 1, os_name: sys.os, - arch: "amd64", + arch: sys.arch, memory: sys.mem, podman: false, updated: new Date().toISOString(), }; } - public async getSystemStatsAsync( - _systemId: string, - _type: string, - perPage: number, - ): Promise { - const baseLoad = rand(10, 60); - return generateTimeSeries(perPage, 1, () => ({ stats: generateSystemStats(baseLoad) })); + public async getSystemStatsAsync(systemId: string, type: string, perPage: number): Promise { + const sys = resolveSystem(systemId); + const intervalMinutes = typeToMinutes[type] ?? 1; + const statsSeries = generateSystemStatsSeries(perPage, sys); + return buildTimeSeries(systemId, perPage, intervalMinutes, type, statsSeries.map((stats) => ({ stats }))); } - public async getContainerStatsAsync( - _systemId: string, - _type: string, - perPage: number, - ): Promise { - const names = containerNames.slice(0, randInt(3, containerNames.length)); - return generateTimeSeries(perPage, 1, () => ({ stats: generateContainerStats(names) })); + public async getContainerStatsAsync(systemId: string, type: string, perPage: number): Promise { + const sys = resolveSystem(systemId); + const intervalMinutes = typeToMinutes[type] ?? 1; + const selectedProfiles = sys.containers.map((i) => containerProfiles[i]).filter(Boolean) as typeof containerProfiles; + const series = generateContainerStatsSeries(perPage, selectedProfiles); + return buildTimeSeries(systemId, perPage, intervalMinutes, type, series.map((stats) => ({ stats }))); } public async getAlertsAsync(): Promise { return [ { id: "mock-alert-1", user: "mock-user", system: "mock-sys-1", name: "CPU", triggered: true, value: 90, min: 5 }, - { - id: "mock-alert-2", - user: "mock-user", - system: "mock-sys-2", - name: "Memory", - triggered: false, - value: 80, - min: 10, - }, - { - id: "mock-alert-3", - user: "mock-user", - system: "mock-sys-1", - name: "Disk", - triggered: false, - value: 85, - min: 15, - }, - { - id: "mock-alert-4", - user: "mock-user", - system: "mock-sys-4", - name: "Temperature", - triggered: true, - value: 75, - min: 3, - }, + { id: "mock-alert-2", user: "mock-user", system: "mock-sys-2", name: "Memory", triggered: false, value: 80, min: 10 }, + { id: "mock-alert-3", user: "mock-user", system: "mock-sys-1", name: "Disk", triggered: false, value: 85, min: 15 }, + { id: "mock-alert-4", user: "mock-user", system: "mock-sys-4", name: "Temperature", triggered: true, value: 75, min: 3 }, ]; } public async getAlertHistoryAsync(_systemId?: string, perPage = 10): Promise { const now = Date.now(); + const names = ["CPU", "Memory", "Disk", "Temperature", "Bandwidth"]; return Array.from({ length: Math.min(perPage, 5) }, (_, i) => ({ id: `mock-history-${i}`, alert: `mock-alert-${(i % 4) + 1}`, user: "mock-user", system: `mock-sys-${(i % 5) + 1}`, - name: ["CPU", "Memory", "Disk", "Temperature", "Bandwidth"][i % 5]!, + name: names[i % names.length] ?? "CPU", val: rand(60, 95), created: new Date(now - (i + 1) * 3_600_000).toISOString(), resolved: i % 2 === 0 ? new Date(now - i * 1_800_000).toISOString() : null,