feat(beszel): add Beszel integration with system monitoring widgets (#5900)
This commit is contained in:
1 parent
5d1f268438
commit
a4db9cf0dc
41 files changed
+3474
-14
No files matched your search
@@ -0,0 +1,122 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
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";
|
||||
|
||||
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 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);
|
||||
}),
|
||||
);
|
||||
return enriched;
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "seconds"),
|
||||
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 instance = await createIntegrationAsync(integration);
|
||||
const alerts = await instance.getAlertsAsync();
|
||||
const history = input.includeHistory ? await instance.getAlertHistoryAsync(undefined, input.maxHistoryItems) : [];
|
||||
return { alerts, history };
|
||||
},
|
||||
cacheDuration: dayjs.duration(15, "seconds"),
|
||||
queryKey: "beszelAlerts",
|
||||
});
|
||||
|
||||
const timePeriodConfig: Record<string, { type: string; perPage: number }> = {
|
||||
"1h": { type: "1m", perPage: 60 },
|
||||
"12h": { type: "10m", perPage: 72 },
|
||||
"24h": { type: "20m", perPage: 72 },
|
||||
"1w": { type: "120m", perPage: 84 },
|
||||
"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 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(() => [])
|
||||
: [];
|
||||
return { systemStats, containerStats };
|
||||
},
|
||||
cacheDuration: dayjs.duration(30, "seconds"),
|
||||
queryKey: "beszelStats",
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-
|
||||
|
||||
export const systemInfoRequestHandler = createCachedIntegrationRequestHandler<
|
||||
SystemHealthMonitoring,
|
||||
Exclude<IntegrationKindByCategory<"healthMonitoring">, "proxmox" | "coolify">,
|
||||
Exclude<IntegrationKindByCategory<"healthMonitoring">, "proxmox" | "coolify" | "beszel">,
|
||||
Record<string, never>
|
||||
>({
|
||||
async requestAsync(integration, _input) {
|
||||
|
||||
Reference in new issue
Block a user