+12








fcd7c979df
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>
159 lines
5.7 KiB
TypeScript
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 };
|
|
},
|
|
});
|