Files
homarr/packages/request-handler/src/beszel.ts
T
+12 fcd7c979df feat!: release v2 (#6545)
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com>
Co-authored-by: Danura30082 <danura30082+github@gmail.com>
Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com>
Co-authored-by: ajnart <thomas@ajnart.fr>
Co-authored-by: ajnart <thomas@ajnart.dev>
Co-authored-by: chenjun <chenjun@foxmail.com>
Co-authored-by: Wikibear <wikibear@users.noreply.github.com>
Co-authored-by: Bergasha <bergasha@gmail.com>
Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com>
Co-authored-by: Lucas Araujo <lucas.lra@gmail.com>
Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com>
Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social>
Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com>
Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com>
Co-authored-by: thrawnx <tight.bulb9384@fastmail.com>
Co-authored-by: Chris <58918656+nordada@users.noreply.github.com>
Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com>
Co-authored-by: Yanis <naamaneyanis@gmail.com>
Co-authored-by: Wikibear <me@sej.de>
Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
2026-10-02 21:00:14 +02:00

159 lines
5.7 KiB
TypeScript

import { createLogger } from "@homarr/core/infrastructure/logs";
import { createIntegrationAsync } from "@homarr/integrations/factory";
import type {
BeszelAlert,
BeszelAlertHistory,
BeszelContainerStatsRecord,
BeszelSystem,
BeszelSystemDetails,
BeszelSystemRow,
BeszelSystemStatsRecord,
} from "@homarr/integrations/types";
import { createIntegrationRequestHandler } from "./lib/integration-request-handler";
const logger = createLogger({ module: "beszelRequestHandler" });
const MEBIBYTE = 1024 ** 2;
export type { BeszelSystemRow } from "@homarr/integrations/types";
/**
* Maps raw Beszel API responses (BeszelSystem + BeszelSystemDetails) into a
* normalized BeszelSystemRow for widget consumption.
*
* BeszelSystemInfo uses short field names (see beszel-types.ts for full docs):
* cpu=CPU%, mp=memory%, dp=disk%, g=GPU%, la=loadAvg, u=uptime(s), v=version,
* bb=bandwidth(bytes/s), b=bandwidth(MiB/s legacy), dt=disk temp, bat=battery,
* sv=[running,total] services, h=hostname, m=CPU model, c=cores, ct=threads
*/
function mapToSystemRow(system: BeszelSystem, details: BeszelSystemDetails | null): BeszelSystemRow {
const info = system.info;
return {
id: system.id,
name: system.name,
status: system.status,
cpu: info.cpu,
memory: info.mp,
disk: info.dp,
extraFilesystems: info.efs ?? {},
gpu: info.g ?? 0,
loadAvg: info.la ?? null,
// bb = bytes/s (newer), b = MiB/s (legacy, normalize to bytes/s)
netBytes: info.bb ?? (info.b ?? 0) * MEBIBYTE,
temp: info.dt ?? null,
battery: info.bat ?? null,
services: info.sv?.[0] ?? 0,
uptime: info.u,
agentVersion: info.v,
hostname: details?.hostname ?? info.h ?? system.name,
cpuModel: details?.cpu ?? info.m ?? "Unknown",
cores: details?.cores ?? info.ct ?? info.c ?? 1,
memoryTotal: details?.memory ?? 0,
osName: details?.os_name ?? "",
};
}
export const beszelSystemsRequestHandler = createIntegrationRequestHandler<
BeszelSystemRow[],
"beszel" | "mock",
Record<string, never>
>({
cacheNamespace: "beszel:systems",
async requestAsync(integration) {
const start = performance.now();
const instance = await createIntegrationAsync(integration);
const systems = await instance.getSystemsAsync();
const enriched = await Promise.all(
systems.map(async (system) => {
const details = await instance.getSystemDetailsAsync(system.id).catch((error) => {
logger.warn("Failed to fetch Beszel system details", { systemId: system.id, error: String(error) });
return null;
});
return mapToSystemRow(system, details);
}),
);
logger.debug("beszelSystems fetch completed", {
integrationId: integration.id,
durationMs: Math.round(performance.now() - start),
count: enriched.length,
});
return enriched;
},
});
export interface BeszelAlertsData {
alerts: BeszelAlert[];
history: BeszelAlertHistory[];
}
export const beszelAlertsRequestHandler = createIntegrationRequestHandler<
BeszelAlertsData,
"beszel" | "mock",
{ includeHistory: boolean; maxHistoryItems: number }
>({
cacheNamespace: "beszel:alerts",
async requestAsync(integration, input) {
const start = performance.now();
const instance = await createIntegrationAsync(integration);
const alerts = await instance.getAlertsAsync();
const history = input.includeHistory ? await instance.getAlertHistoryAsync(undefined, input.maxHistoryItems) : [];
logger.debug("beszelAlerts fetch completed", {
integrationId: integration.id,
durationMs: Math.round(performance.now() - start),
alertsCount: alerts.length,
historyCount: history.length,
});
return { alerts, history };
},
});
const timePeriodConfig: Record<string, { type: string; perPage: number }> = {
"1m": { type: "1m", perPage: 60 },
"1h": { type: "1m", perPage: 60 },
"12h": { type: "10m", perPage: 72 },
"24h": { type: "20m", perPage: 72 },
"1w": { type: "480m", perPage: 21 },
"30d": { type: "480m", perPage: 90 },
};
export interface BeszelStatsData {
systemStats: BeszelSystemStatsRecord[];
containerStats: BeszelContainerStatsRecord[];
}
export const beszelStatsRequestHandler = createIntegrationRequestHandler<
BeszelStatsData,
"beszel" | "mock",
{ systemId: string; timePeriod: string; includeDocker: boolean }
>({
cacheNamespace: "beszel:stats",
// No cache — the widget polls every 5s for live updates;
// a TTL here would serve stale records between polls.
cacheTtlMs: 0,
async requestAsync(integration, input) {
const start = performance.now();
const config = timePeriodConfig[input.timePeriod] ?? { type: "1m", perPage: 60 };
const instance = await createIntegrationAsync(integration);
const systemStatsPromise = instance.getSystemStatsAsync(input.systemId, config.type, config.perPage);
let containerStatsPromise: Promise<BeszelContainerStatsRecord[]> = Promise.resolve([]);
if (input.includeDocker) {
containerStatsPromise = instance
.getContainerStatsAsync(input.systemId, config.type, config.perPage)
.catch((error) => {
logger.warn("Failed to fetch Beszel container stats", { systemId: input.systemId, error: String(error) });
return [];
});
}
const [systemStats, containerStats] = await Promise.all([systemStatsPromise, containerStatsPromise]);
logger.debug("beszelStats fetch completed", {
integrationId: integration.id,
systemId: input.systemId,
timePeriod: input.timePeriod,
durationMs: Math.round(performance.now() - start),
systemStatsCount: systemStats.length,
containerStatsCount: containerStats.length,
});
return { systemStats, containerStats };
},
});