refactor: streamline widget and integration architecture

This commit is contained in:
ajnart committed 2026-08-23 22:33:29 +02:00
1 parent ec295fe26e
commit 425cb653a1
243 files changed
+5722 -2255

No files matched your search

+187 -135
View File
@@ -1,146 +1,198 @@
import type { IntegrationKind } from "@homarr/definitions";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import { AdGuardHomeIntegration } from "../adguard-home/adguard-home-integration";
import { BeszelIntegration } from "../beszel/beszel-integration";
import { BazarrIntegration } from "../bazarr/bazarr-integration";
import { AnchorIntegration } from "../anchor/anchor-integration";
import { AudiobookshelfIntegration } from "../audiobookshelf/audiobookshelf-integration";
import { CoolifyIntegration } from "../coolify/coolify-integration";
import { DashDotIntegration } from "../dashdot/dashdot-integration";
import { Aria2Integration } from "../download-client/aria2/aria2-integration";
import { DelugeIntegration } from "../download-client/deluge/deluge-integration";
import { NzbGetIntegration } from "../download-client/nzbget/nzbget-integration";
import { QBitTorrentIntegration } from "../download-client/qbittorrent/qbittorrent-integration";
import { SabnzbdIntegration } from "../download-client/sabnzbd/sabnzbd-integration";
import { SlskdIntegration } from "../download-client/slskd/slskd-integration";
import { TransmissionIntegration } from "../download-client/transmission/transmission-integration";
import { EmbyIntegration } from "../emby/emby-integration";
import { GlancesIntegration } from "../glances/glances-integration";
import { ArchiveTeamWarriorIntegration } from "../archive-team-warrior/archive-team-warrior-integration";
import { GluetunIntegration } from "../gluetun/gluetun-integration";
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
import { ICalIntegration } from "../ical/ical-integration";
import { ImmichIntegration } from "../immich/immich-integration";
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
import { LidarrIntegration } from "../media-organizer/lidarr/lidarr-integration";
import { RadarrIntegration } from "../media-organizer/radarr/radarr-integration";
import { ReadarrIntegration } from "../media-organizer/readarr/readarr-integration";
import { SonarrIntegration } from "../media-organizer/sonarr/sonarr-integration";
import { TdarrIntegration } from "../media-transcoding/tdarr-integration";
import { MockIntegration } from "../mock/mock-integration";
import { NavidromeIntegration } from "../navidrome/navidrome-integration";
import { NextcloudIntegration } from "../nextcloud/nextcloud.integration";
import { GotifyIntegration } from "../gotify/gotify-integration";
import { NTFYIntegration } from "../ntfy/ntfy-integration";
import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration";
import { OPNsenseIntegration } from "../opnsense/opnsense-integration";
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
import { PaperlessNgxIntegration } from "../paperless-ngx/paperless-ngx-integration";
import { PatchMonIntegration } from "../patchmon/patchmon-integration";
import { PeaNutIntegration } from "../peanut/peanut-integration";
import { createPiHoleIntegrationAsync } from "../pi-hole/pi-hole-integration-factory";
import { createTechnitiumDnsIntegrationAsync } from "../technitium/technitium-integration-factory";
import { PlexIntegration } from "../plex/plex-integration";
import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration";
import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
import { SeerrIntegration } from "../seerr/seerr-integration";
import { SpeedtestTrackerIntegration } from "../speedtest-tracker/speedtest-tracker-integration";
import { TracearrIntegration } from "../tracearr/tracearr-integration";
import { TraefikIntegration } from "../traefik/traefik-integration";
import { SynologyIntegration } from "../synology/synology-integration";
import { TrueNasIntegration } from "../truenas/truenas-integration";
import { UmamiIntegration } from "../umami/umami-integration";
import { UptimeKumaIntegration } from "../uptime-kuma/uptime-kuma-integration";
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
import { UnraidIntegration } from "../unraid/unraid-integration";
import { WudIntegration } from "../wud/wud-integration";
import { generatedIntegrationCreatorLoaders } from "../generated/integration-creators";
import type { Integration, IntegrationInput } from "./integration";
export const createIntegrationAsync = async <TKind extends keyof typeof integrationCreators>(
const logger = createLogger({ module: "integrationFactory" });
type IntegrationCreator<TIntegration extends Integration = Integration> = (
input: IntegrationInput,
) => Promise<TIntegration>;
type IntegrationConstructor<TIntegration extends Integration = Integration> = new (
input: IntegrationInput,
) => TIntegration;
const createConstructorCreator =
<TIntegration extends Integration>(
IntegrationClass: IntegrationConstructor<TIntegration>,
): IntegrationCreator<TIntegration> =>
async (input) =>
new IntegrationClass(input);
// Keep each import path explicit so Node and Turbopack can discover every lazy integration chunk.
const integrationCreatorLoaders = {
anchor: async () => createConstructorCreator((await import("../anchor/anchor-integration")).AnchorIntegration),
piHole: async () => (await import("../pi-hole/pi-hole-integration-factory")).createPiHoleIntegrationAsync,
adGuardHome: async () =>
createConstructorCreator((await import("../adguard-home/adguard-home-integration")).AdGuardHomeIntegration),
technitiumDns: async () =>
(await import("../technitium/technitium-integration-factory")).createTechnitiumDnsIntegrationAsync,
homeAssistant: async () =>
createConstructorCreator((await import("../homeassistant/homeassistant-integration")).HomeAssistantIntegration),
jellyfin: async () =>
createConstructorCreator((await import("../jellyfin/jellyfin-integration")).JellyfinIntegration),
plex: async () => createConstructorCreator((await import("../plex/plex-integration")).PlexIntegration),
sonarr: async () =>
createConstructorCreator((await import("../media-organizer/sonarr/sonarr-integration")).SonarrIntegration),
radarr: async () =>
createConstructorCreator((await import("../media-organizer/radarr/radarr-integration")).RadarrIntegration),
sabNzbd: async () =>
createConstructorCreator((await import("../download-client/sabnzbd/sabnzbd-integration")).SabnzbdIntegration),
nzbGet: async () =>
createConstructorCreator((await import("../download-client/nzbget/nzbget-integration")).NzbGetIntegration),
qBittorrent: async () =>
createConstructorCreator(
(await import("../download-client/qbittorrent/qbittorrent-integration")).QBitTorrentIntegration,
),
deluge: async () =>
createConstructorCreator((await import("../download-client/deluge/deluge-integration")).DelugeIntegration),
transmission: async () =>
createConstructorCreator(
(await import("../download-client/transmission/transmission-integration")).TransmissionIntegration,
),
slskd: async () =>
createConstructorCreator((await import("../download-client/slskd/slskd-integration")).SlskdIntegration),
aria2: async () =>
createConstructorCreator((await import("../download-client/aria2/aria2-integration")).Aria2Integration),
jellyseerr: async () =>
createConstructorCreator((await import("../jellyseerr/jellyseerr-integration")).JellyseerrIntegration),
seerr: async () => createConstructorCreator((await import("../seerr/seerr-integration")).SeerrIntegration),
overseerr: async () =>
createConstructorCreator((await import("../overseerr/overseerr-integration")).OverseerrIntegration),
prowlarr: async () =>
createConstructorCreator((await import("../prowlarr/prowlarr-integration")).ProwlarrIntegration),
openmediavault: async () =>
createConstructorCreator((await import("../openmediavault/openmediavault-integration")).OpenMediaVaultIntegration),
lidarr: async () =>
createConstructorCreator((await import("../media-organizer/lidarr/lidarr-integration")).LidarrIntegration),
readarr: async () =>
createConstructorCreator((await import("../media-organizer/readarr/readarr-integration")).ReadarrIntegration),
dashDot: async () => createConstructorCreator((await import("../dashdot/dashdot-integration")).DashDotIntegration),
tdarr: async () =>
createConstructorCreator((await import("../media-transcoding/tdarr-integration")).TdarrIntegration),
proxmox: async () => createConstructorCreator((await import("../proxmox/proxmox-integration")).ProxmoxIntegration),
emby: async () => createConstructorCreator((await import("../emby/emby-integration")).EmbyIntegration),
nextcloud: async () =>
createConstructorCreator((await import("../nextcloud/nextcloud.integration")).NextcloudIntegration),
unifiController: async () =>
createConstructorCreator(
(await import("../unifi-controller/unifi-controller-integration")).UnifiControllerIntegration,
),
opnsense: async () =>
createConstructorCreator((await import("../opnsense/opnsense-integration")).OPNsenseIntegration),
ical: async () => createConstructorCreator((await import("../ical/ical-integration")).ICalIntegration),
ntfy: async () => createConstructorCreator((await import("../ntfy/ntfy-integration")).NTFYIntegration),
gotify: async () => createConstructorCreator((await import("../gotify/gotify-integration")).GotifyIntegration),
mock: async () => createConstructorCreator((await import("../mock/mock-integration")).MockIntegration),
truenas: async () => createConstructorCreator((await import("../truenas/truenas-integration")).TrueNasIntegration),
synology: async () =>
createConstructorCreator((await import("../synology/synology-integration")).SynologyIntegration),
unraid: async () => createConstructorCreator((await import("../unraid/unraid-integration")).UnraidIntegration),
coolify: async () => createConstructorCreator((await import("../coolify/coolify-integration")).CoolifyIntegration),
tracearr: async () =>
createConstructorCreator((await import("../tracearr/tracearr-integration")).TracearrIntegration),
glances: async () => createConstructorCreator((await import("../glances/glances-integration")).GlancesIntegration),
immich: async () => createConstructorCreator((await import("../immich/immich-integration")).ImmichIntegration),
paperlessNgx: async () =>
createConstructorCreator((await import("../paperless-ngx/paperless-ngx-integration")).PaperlessNgxIntegration),
patchmon: async () =>
createConstructorCreator((await import("../patchmon/patchmon-integration")).PatchMonIntegration),
speedtestTracker: async () =>
createConstructorCreator(
(await import("../speedtest-tracker/speedtest-tracker-integration")).SpeedtestTrackerIntegration,
),
audiobookshelf: async () =>
createConstructorCreator((await import("../audiobookshelf/audiobookshelf-integration")).AudiobookshelfIntegration),
navidrome: async () =>
createConstructorCreator((await import("../navidrome/navidrome-integration")).NavidromeIntegration),
umami: async () => createConstructorCreator((await import("../umami/umami-integration")).UmamiIntegration),
gluetun: async () => createConstructorCreator((await import("../gluetun/gluetun-integration")).GluetunIntegration),
archiveTeamWarrior: async () =>
createConstructorCreator(
(await import("../archive-team-warrior/archive-team-warrior-integration")).ArchiveTeamWarriorIntegration,
),
uptimeKuma: async () =>
createConstructorCreator((await import("../uptime-kuma/uptime-kuma-integration")).UptimeKumaIntegration),
peaNut: async () => createConstructorCreator((await import("../peanut/peanut-integration")).PeaNutIntegration),
...generatedIntegrationCreatorLoaders,
bazarr: async () => createConstructorCreator((await import("../bazarr/bazarr-integration")).BazarrIntegration),
traefik: async () => createConstructorCreator((await import("../traefik/traefik-integration")).TraefikIntegration),
wud: async () => createConstructorCreator((await import("../wud/wud-integration")).WudIntegration),
};
type AssertIntegrationCreatorLoaders<TLoaders extends Record<IntegrationKind, () => Promise<IntegrationCreator>>> =
TLoaders;
type AssertNoUnexpectedIntegrationKinds<TKind extends never> = TKind;
type IntegrationCreatorLoadersCheck = AssertIntegrationCreatorLoaders<typeof integrationCreatorLoaders>;
type UnexpectedIntegrationKindsCheck = AssertNoUnexpectedIntegrationKinds<
Exclude<keyof typeof integrationCreatorLoaders, IntegrationKind>
>;
type IntegrationCreatorLoaders = IntegrationCreatorLoadersCheck & Record<UnexpectedIntegrationKindsCheck, never>;
type IntegrationCreatorOfKind<TKind extends keyof IntegrationCreatorLoaders> = Awaited<
ReturnType<IntegrationCreatorLoaders[TKind]>
>;
type IntegrationInstanceOfKind<TKind extends keyof IntegrationCreatorLoaders> = Awaited<
ReturnType<IntegrationCreatorOfKind<TKind>>
>;
const integrationCreatorCache = new Map<IntegrationKind, Promise<IntegrationCreator>>();
const loadIntegrationCreatorAsync = async <TKind extends keyof IntegrationCreatorLoaders>(
kind: TKind,
): Promise<IntegrationCreatorOfKind<TKind>> => {
const cachedCreator = integrationCreatorCache.get(kind);
if (cachedCreator) {
return (await cachedCreator) as IntegrationCreatorOfKind<TKind>;
}
const startedAt = Date.now();
logger.debug("Loading integration implementation", { integrationKind: kind });
const creatorPromise = integrationCreatorLoaders[kind]();
integrationCreatorCache.set(kind, creatorPromise);
try {
const creator = (await creatorPromise) as IntegrationCreatorOfKind<TKind>;
logger.debug("Integration implementation loaded", {
integrationKind: kind,
durationMs: Date.now() - startedAt,
});
return creator;
} catch (error) {
if (integrationCreatorCache.get(kind) === creatorPromise) {
integrationCreatorCache.delete(kind);
}
logger.warn(
new ErrorWithMetadata(
"Integration implementation failed to load",
{
integrationKind: kind,
durationMs: Date.now() - startedAt,
},
{ cause: error },
),
);
throw error;
}
};
export const createIntegrationAsync = async <TKind extends keyof IntegrationCreatorLoaders>(
integration: IntegrationInput & { kind: TKind },
) => {
if (!(integration.kind in integrationCreators)) {
): Promise<IntegrationInstanceOfKind<TKind>> => {
if (!Object.hasOwn(integrationCreatorLoaders, integration.kind)) {
logger.warn("Integration client creation refused", {
integrationId: integration.id,
integrationKind: integration.kind,
reason: "integration-kind-unregistered",
});
throw new Error(
`Unknown integration kind ${integration.kind}. Did you forget to add it to the integration creator?`,
);
}
const creator = integrationCreators[integration.kind];
// factories are an array, to differentiate in js between class constructors and functions
if (Array.isArray(creator)) {
return (await creator[0](integration)) as IntegrationInstanceOfKind<TKind>;
}
return new creator(integration) as IntegrationInstanceOfKind<TKind>;
const creator = await loadIntegrationCreatorAsync(integration.kind);
return (await creator(integration)) as IntegrationInstanceOfKind<TKind>;
};
type IntegrationInstance = new (integration: IntegrationInput) => Integration;
// factories are an array, to differentiate in js between class constructors and functions
export const integrationCreators = {
anchor: AnchorIntegration,
piHole: [createPiHoleIntegrationAsync],
adGuardHome: AdGuardHomeIntegration,
technitiumDns: [createTechnitiumDnsIntegrationAsync],
homeAssistant: HomeAssistantIntegration,
jellyfin: JellyfinIntegration,
plex: PlexIntegration,
sonarr: SonarrIntegration,
radarr: RadarrIntegration,
sabNzbd: SabnzbdIntegration,
nzbGet: NzbGetIntegration,
qBittorrent: QBitTorrentIntegration,
deluge: DelugeIntegration,
transmission: TransmissionIntegration,
slskd: SlskdIntegration,
aria2: Aria2Integration,
jellyseerr: JellyseerrIntegration,
seerr: SeerrIntegration,
overseerr: OverseerrIntegration,
prowlarr: ProwlarrIntegration,
openmediavault: OpenMediaVaultIntegration,
lidarr: LidarrIntegration,
readarr: ReadarrIntegration,
dashDot: DashDotIntegration,
tdarr: TdarrIntegration,
proxmox: ProxmoxIntegration,
emby: EmbyIntegration,
nextcloud: NextcloudIntegration,
unifiController: UnifiControllerIntegration,
opnsense: OPNsenseIntegration,
ical: ICalIntegration,
ntfy: NTFYIntegration,
gotify: GotifyIntegration,
mock: MockIntegration,
truenas: TrueNasIntegration,
synology: SynologyIntegration,
unraid: UnraidIntegration,
coolify: CoolifyIntegration,
tracearr: TracearrIntegration,
glances: GlancesIntegration,
immich: ImmichIntegration,
paperlessNgx: PaperlessNgxIntegration,
patchmon: PatchMonIntegration,
speedtestTracker: SpeedtestTrackerIntegration,
audiobookshelf: AudiobookshelfIntegration,
navidrome: NavidromeIntegration,
umami: UmamiIntegration,
gluetun: GluetunIntegration,
archiveTeamWarrior: ArchiveTeamWarriorIntegration,
uptimeKuma: UptimeKumaIntegration,
peaNut: PeaNutIntegration,
beszel: BeszelIntegration,
bazarr: BazarrIntegration,
traefik: TraefikIntegration,
wud: WudIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
[kind in TKind]: (typeof integrationCreators)[kind] extends [(input: IntegrationInput) => Promise<Integration>]
? Awaited<ReturnType<(typeof integrationCreators)[kind][0]>>
: (typeof integrationCreators)[kind] extends IntegrationInstance
? InstanceType<(typeof integrationCreators)[kind]>
: never;
}[TKind];
@@ -1,14 +1,18 @@
import { isFunction } from "@homarr/common";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import type { Integration } from "../integration";
import type { IIntegrationErrorHandler } from "./handler";
import { integrationFetchHttpErrorHandler } from "./http";
import { IntegrationError } from "./integration-error";
import { IntegrationUnknownError } from "./integration-unknown-error";
import { IntegrationRequestError } from "./http/integration-request-error";
import { IntegrationResponseError } from "./http/integration-response-error";
import { integrationJsonParseErrorHandler, integrationZodParseErrorHandler } from "./parse";
const logger = createLogger({ module: "handleIntegrationErrors" });
const loggedIntegrationErrors = new WeakSet<IntegrationError>();
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-explicit-any
type AbstractConstructor<T = {}> = abstract new (...args: any[]) => T;
@@ -19,6 +23,62 @@ const defaultErrorHandlers: IIntegrationErrorHandler[] = [
integrationFetchHttpErrorHandler,
];
interface IntegrationOperationMetadata {
operation: string;
integrationId: string;
integrationKind: string;
integrationName: string;
}
const safeErrorNameRegex = /^(?:Error|[A-Za-z][A-Za-z0-9]*(?:Error|Err))$/;
const createSafeErrorCause = (error: unknown, depth = 0): Error => {
let options: ErrorOptions | undefined;
if (depth < 5 && error instanceof Error && error.cause !== undefined) {
options = { cause: createSafeErrorCause(error.cause, depth + 1) };
}
const safeError = new Error("Integration operation failed", options);
if (error instanceof Error && safeErrorNameRegex.test(error.name)) {
safeError.name = error.name;
}
return safeError;
};
const logFailure = (
error: IntegrationError,
failureType: "normalized" | "unknown",
metadata: IntegrationOperationMetadata,
durationMs: number,
) => {
if (loggedIntegrationErrors.has(error)) {
return;
}
loggedIntegrationErrors.add(error);
const errorMetadata: Record<string, unknown> = { errorType: error.name };
if (error instanceof IntegrationRequestError) {
errorMetadata.requestType = error.cause.type;
errorMetadata.requestReason = error.cause.reason;
errorMetadata.requestCode = error.cause.code;
}
if (error instanceof IntegrationResponseError) {
errorMetadata.statusCode = error.cause.statusCode;
}
logger.warn(
new ErrorWithMetadata(
"Integration operation failed",
{
...metadata,
durationMs,
failureType,
...errorMetadata,
},
{ cause: createSafeErrorCause(error) },
),
);
};
export const HandleIntegrationErrors = (errorHandlers: IIntegrationErrorHandler[]) => {
const combinedErrorHandlers = [...defaultErrorHandlers, ...errorHandlers];
return <T extends AbstractConstructor<Integration>>(IntegrationBaseClass: T): T => {
@@ -44,8 +104,27 @@ export const HandleIntegrationErrors = (errorHandlers: IIntegrationErrorHandler[
processedProperties.add(propertyKey);
const wrapped = (...methodArgs: unknown[]) => {
const handleError = (error: unknown) => {
const startedAt = Date.now();
const operationMetadata: IntegrationOperationMetadata = {
operation: propertyKey,
integrationId: this.publicIntegration.id,
integrationKind: this.integration.kind ?? "unknown",
integrationName: this.publicIntegration.name,
};
logger.debug("Integration operation started", operationMetadata);
const logCompletion = () => {
logger.debug("Integration operation completed", {
...operationMetadata,
durationMs: Date.now() - startedAt,
});
};
const handleError = (error: unknown): never => {
if (error instanceof IntegrationError) {
const failureType = error instanceof IntegrationUnknownError ? "unknown" : "normalized";
logFailure(error, failureType, operationMetadata, Date.now() - startedAt);
throw error;
}
@@ -53,26 +132,29 @@ export const HandleIntegrationErrors = (errorHandlers: IIntegrationErrorHandler[
const handledError = handler.handleError(error, this.publicIntegration);
if (!handledError) continue;
logFailure(handledError, "normalized", operationMetadata, Date.now() - startedAt);
throw handledError;
}
// If the error was handled and should be thrown again, throw it
logger.debug("Unhandled error in integration", {
error: error instanceof Error ? `${error.name}: ${error.message}` : undefined,
integrationName: this.publicIntegration.name,
});
throw new IntegrationUnknownError(this.publicIntegration, { cause: error });
const unknownError = new IntegrationUnknownError(this.publicIntegration, { cause: error });
logFailure(unknownError, "unknown", operationMetadata, Date.now() - startedAt);
throw unknownError;
};
try {
const result = original.apply(this, methodArgs);
if (result instanceof Promise) {
return result.catch((error: unknown) => {
handleError(error);
});
return result.then(
(value: unknown) => {
logCompletion();
return value;
},
(error: unknown) => handleError(error),
);
}
logCompletion();
return result;
} catch (error: unknown) {
handleError(error);
@@ -5,7 +5,7 @@ import { fetch as undiciFetch } from "undici";
import { removeTrailingSlash } from "@homarr/common";
import { createAxiosCertificateInstanceAsync, createCertificateAgentAsync } from "@homarr/core/infrastructure/http";
import type { IntegrationSecretKind } from "@homarr/definitions";
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
import { HandleIntegrationErrors } from "./errors/decorator";
import { TestConnectionError } from "./test-connection/test-connection-error";
@@ -15,6 +15,7 @@ import type { IntegrationSecret } from "./types";
export interface IntegrationInput {
id: string;
kind?: IntegrationKind;
name: string;
url: string;
externalUrl: string | null;
Binary file not shown.

After

Width:  |  Height:  |  Size: 948 KiB

@@ -0,0 +1,58 @@
---
title: "Beszel"
description: "Beszel is a lightweight server monitoring platform with Docker stats, historical data, and alert functions."
hide_title: true
---
import { IntegrationHeader } from "@site/src/components/integrations/header";
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
import { AddingIntegration } from "@site/src/components/integrations/adding";
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
import { beszelIntegration } from ".";
import { beszelSystemStatsWidget } from "@site/docs/widgets/beszel-system-stats";
import { beszelSystemGridWidget } from "@site/docs/widgets/beszel-system-grid";
import { beszelSystemTableWidget } from "@site/docs/widgets/beszel-system-table";
import { beszelAlertsWidget } from "@site/docs/widgets/beszel-alerts";
import beszelDashboard from "./img/beszel-dashboard.png";
<IntegrationHeader integration={beszelIntegration} categories={["System Monitoring"]} />
Beszel monitors your servers in real time via lightweight agents, collecting CPU, memory, disk, network, GPU, temperature, and Docker container metrics. It stores historical data and supports configurable alerts.
### Screenshots
<img src={beszelDashboard} alt="Beszel dashboard overview showing all four widgets" />
### Widgets & Capabilities
<IntegrationCapabilites
items={[
{ widget: beszelSystemStatsWidget },
{ widget: beszelSystemGridWidget },
{ widget: beszelSystemTableWidget },
{ widget: beszelAlertsWidget },
]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets
secrets={[
{
credentials: ["username", "password"],
steps: [
"Open your Beszel hub instance (default port 8090)",
"Use the same credentials you use to log in to the Beszel web UI",
],
},
]}
/>
:::tip
Beszel uses PocketBase under the hood. The default admin credentials are set during the initial Beszel setup.
:::
@@ -0,0 +1,7 @@
export const beszelIntegration = {
name: "Beszel",
description:
"Beszel is a lightweight server monitoring platform with Docker stats, historical data, and alert functions.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/beszel.svg",
path: "../../integrations/beszel",
} as const;
@@ -0,0 +1,19 @@
import { defineIntegrationModule } from "@homarr/definitions";
export default defineIntegrationModule({
kind: "beszel",
name: "Beszel",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/beszel.svg",
secretKinds: [["username", "password"]],
categories: ["beszel"],
defaultPort: 8090,
documentation: {
slug: "beszel",
sourceDirectory: "docs",
},
creator: {
type: "constructor",
module: "./beszel-integration",
exportName: "BeszelIntegration",
},
});
+1
View File
@@ -0,0 +1 @@
export { createIntegrationAsync } from "./base/creator";
@@ -0,0 +1,12 @@
// This file is generated by pnpm integrations:sync. Do not edit it directly.
import type { Integration, IntegrationInput } from "../base/integration";
type GeneratedIntegrationCreator = (input: IntegrationInput) => Promise<Integration>;
export const generatedIntegrationCreatorLoaders = {
beszel: async () => {
const IntegrationClass = (await import("../beszel/beszel-integration")).BeszelIntegration;
return async (input: IntegrationInput) => new IntegrationClass(input);
},
} satisfies Record<string, () => Promise<GeneratedIntegrationCreator>>;
+7 -59
View File
@@ -1,53 +1,6 @@
// General integrations
export { ArchiveTeamWarriorIntegration } from "./archive-team-warrior/archive-team-warrior-integration";
export { WudIntegration } from "./wud/wud-integration";
export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration";
export { TechnitiumDnsIntegration } from "./technitium/technitium-integration";
export { AnchorIntegration } from "./anchor/anchor-integration";
export { Aria2Integration } from "./download-client/aria2/aria2-integration";
export { DelugeIntegration } from "./download-client/deluge/deluge-integration";
export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration";
export { QBitTorrentIntegration } from "./download-client/qbittorrent/qbittorrent-integration";
export { SabnzbdIntegration } from "./download-client/sabnzbd/sabnzbd-integration";
export { SlskdIntegration } from "./download-client/slskd/slskd-integration";
export { TransmissionIntegration } from "./download-client/transmission/transmission-integration";
export { HomeAssistantIntegration } from "./homeassistant/homeassistant-integration";
export { JellyfinIntegration } from "./jellyfin/jellyfin-integration";
export { JellyseerrIntegration } from "./jellyseerr/jellyseerr-integration";
export { LidarrIntegration } from "./media-organizer/lidarr/lidarr-integration";
export { RadarrIntegration } from "./media-organizer/radarr/radarr-integration";
export { ReadarrIntegration } from "./media-organizer/readarr/readarr-integration";
export { SonarrIntegration } from "./media-organizer/sonarr/sonarr-integration";
export { NextcloudIntegration } from "./nextcloud/nextcloud.integration";
export { GotifyIntegration } from "./gotify/gotify-integration";
export { NTFYIntegration } from "./ntfy/ntfy-integration";
export { OpenMediaVaultIntegration } from "./openmediavault/openmediavault-integration";
export { GlancesIntegration } from "./glances/glances-integration";
export { OverseerrIntegration } from "./overseerr/overseerr-integration";
export { SeerrIntegration } from "./seerr/seerr-integration";
export { PiHoleIntegrationV5 } from "./pi-hole/v5/pi-hole-integration-v5";
export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
export { PlexIntegration } from "./plex/plex-integration";
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
export { TrueNasIntegration } from "./truenas/truenas-integration";
export { SynologyIntegration } from "./synology/synology-integration";
export { UnraidIntegration } from "./unraid/unraid-integration";
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
export { ICalIntegration } from "./ical/ical-integration";
export { CoolifyIntegration } from "./coolify/coolify-integration";
export { ImmichIntegration } from "./immich/immich-integration";
export { TracearrIntegration } from "./tracearr/tracearr-integration";
export { SpeedtestTrackerIntegration } from "./speedtest-tracker/speedtest-tracker-integration";
export { AudiobookshelfIntegration } from "./audiobookshelf/audiobookshelf-integration";
export { NavidromeIntegration } from "./navidrome/navidrome-integration";
export { UptimeKumaIntegration } from "./uptime-kuma/uptime-kuma-integration";
export { UmamiIntegration } from "./umami/umami-integration";
export { PeaNutIntegration } from "./peanut/peanut-integration";
export { BazarrIntegration } from "./bazarr/bazarr-integration";
export { TraefikIntegration } from "./traefik/traefik-integration";
// Types
// Type-only compatibility surface. Runtime capabilities have explicit subpaths.
export type { IntegrationInput } from "./base/integration";
export type { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
export type { DownloadClientJobsAndStatus } from "./interfaces/downloads/download-client-data";
export type { ExtendedDownloadClientItem } from "./interfaces/downloads/download-client-items";
export type { ExtendedClientStatus } from "./interfaces/downloads/download-client-status";
@@ -59,8 +12,11 @@ export type {
FirewallMemorySummary,
} from "./interfaces/firewall-summary/firewall-summary-types";
export type { SystemHealthMonitoring } from "./interfaces/health-monitoring/health-monitoring-types";
export { UpstreamMediaRequestStatus } from "./interfaces/media-requests/media-request-types";
export type { MediaRequestList, MediaRequestStats } from "./interfaces/media-requests/media-request-types";
export type {
MediaRequestList,
MediaRequestStats,
UpstreamMediaRequestStatus,
} from "./interfaces/media-requests/media-request-types";
export type { StreamSession } from "./interfaces/media-server/media-server-types";
export type { EntityState } from "./interfaces/smart-home/smart-home-types";
export type {
@@ -95,12 +51,4 @@ export type {
} from "./archive-team-warrior/archive-team-warrior-types";
export type { WudStats, WudContainerUpdate } from "./wud/wud-types";
// Schemas
export { anchorNotesListInputSchema } from "./anchor/anchor-types";
export { anchorNoteUpdateInputSchema } from "./anchor/anchor-types";
export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items";
// Helpers
export { createIntegrationAsync } from "./base/creator";
export { simulateResponseContractAsync } from "./base/response-contract";
export type { ResponseContractFixture, ResponseContractParser } from "./base/response-contract";