155 lines
5.3 KiB
TypeScript
155 lines
5.3 KiB
TypeScript
import dayjs from "dayjs";
|
|
|
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
|
import { createIntegrationAsync } from "@homarr/integrations";
|
|
import type {
|
|
BeszelAlert,
|
|
BeszelAlertHistory,
|
|
BeszelContainerStatsRecord,
|
|
BeszelSystem,
|
|
BeszelSystemDetails,
|
|
BeszelSystemRow,
|
|
BeszelSystemStatsRecord,
|
|
} from "@homarr/integrations/types";
|
|
|
|
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
|
|
|
|
const logger = createLogger({ module: "beszelRequestHandler" });
|
|
|
|
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(Mbps 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,
|
|
gpu: info.g ?? 0,
|
|
loadAvg: info.la ?? null,
|
|
// bb = bytes/s (newer), b = Mbps (legacy, multiply to get bytes/s)
|
|
netBytes: info.bb ?? (info.b ?? 0) * 1_000_000,
|
|
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 = createCachedIntegrationRequestHandler<
|
|
BeszelSystemRow[],
|
|
"beszel" | "mock",
|
|
Record<string, never>
|
|
>({
|
|
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(() => null);
|
|
return mapToSystemRow(system, details);
|
|
}),
|
|
);
|
|
logger.debug("beszelSystems fetch completed", {
|
|
integrationId: integration.id,
|
|
durationMs: Math.round(performance.now() - start),
|
|
count: enriched.length,
|
|
});
|
|
return enriched;
|
|
},
|
|
cacheDuration: dayjs.duration(1, "second"),
|
|
fallbackToStaleOnError: true,
|
|
retry: { attempts: 2, delayMs: 500 },
|
|
queryKey: "beszelSystems",
|
|
});
|
|
|
|
export interface BeszelAlertsData {
|
|
alerts: BeszelAlert[];
|
|
history: BeszelAlertHistory[];
|
|
}
|
|
|
|
export const beszelAlertsRequestHandler = createCachedIntegrationRequestHandler<
|
|
BeszelAlertsData,
|
|
"beszel" | "mock",
|
|
{ includeHistory: boolean; maxHistoryItems: number }
|
|
>({
|
|
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 };
|
|
},
|
|
cacheDuration: dayjs.duration(1, "second"),
|
|
fallbackToStaleOnError: true,
|
|
retry: { attempts: 2, delayMs: 500 },
|
|
queryKey: "beszelAlerts",
|
|
});
|
|
|
|
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 = createCachedIntegrationRequestHandler<
|
|
BeszelStatsData,
|
|
"beszel" | "mock",
|
|
{ systemId: string; timePeriod: string; includeDocker: boolean }
|
|
>({
|
|
async requestAsync(integration, input) {
|
|
const start = performance.now();
|
|
const config = timePeriodConfig[input.timePeriod] ?? { type: "1m", perPage: 60 };
|
|
const instance = await createIntegrationAsync(integration);
|
|
const systemStats = await instance.getSystemStatsAsync(input.systemId, config.type, config.perPage);
|
|
const containerStats = input.includeDocker
|
|
? await instance.getContainerStatsAsync(input.systemId, config.type, config.perPage).catch(() => [])
|
|
: [];
|
|
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 };
|
|
},
|
|
cacheDuration: dayjs.duration(1, "second"),
|
|
fallbackToStaleOnError: true,
|
|
retry: { attempts: 2, delayMs: 500 },
|
|
queryKey: "beszelStats",
|
|
});
|