diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx index 5be6b2996..d67002432 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx @@ -79,14 +79,16 @@ export const createBoardContentPage = >( const userId = session?.user.id ?? "anonymous"; return ( - - - - - - - - + <> + + + + + + + + + ); }, generateMetadataAsync: async ({ params }: { params: Promise }): Promise => { diff --git a/packages/api/src/router/widgets/beszel.ts b/packages/api/src/router/widgets/beszel.ts index 0273f6896..66516b372 100644 --- a/packages/api/src/router/widgets/beszel.ts +++ b/packages/api/src/router/widgets/beszel.ts @@ -11,11 +11,14 @@ import { beszelSystemsRequestHandler, } from "@homarr/request-handler/beszel"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; import { createTRPCRouter, publicProcedure } from "../../trpc"; const logger = createLogger({ module: "beszelRouter" }); +const errorMessage = (error: unknown) => (error instanceof Error ? error.message : String(error)); + export const beszelRouter = createTRPCRouter({ getSystems: publicProcedure .meta({ @@ -29,8 +32,9 @@ export const beszelRouter = createTRPCRouter({ .query(async ({ ctx }) => { const integrationIds = ctx.integrations.map((i) => i.id); logger.debug("getSystems called", { userId: ctx.session?.user?.id, integrationIds }); - const settled = await Promise.allSettled( - ctx.integrations.map(async (integration) => { + const results = await settleIntegrationQueries( + ctx.integrations, + async (integration) => { const innerHandler = beszelSystemsRequestHandler.handler(integration, {}); const { data, timestamp } = await innerHandler.getDataAsync(); return { @@ -40,25 +44,18 @@ export const beszelRouter = createTRPCRouter({ systems: data, updatedAt: timestamp, }; - }), + }, + { + fallback: (integration, error) => ({ + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + systems: [], + updatedAt: new Date(0), + error: errorMessage(error), + }), + }, ); - const results = settled.map((result, index) => { - if (result.status === "fulfilled") return result.value; - const integration = ctx.integrations[index]; - logger.warn("getSystems integration failed", { - userId: ctx.session?.user?.id, - integrationId: integration?.id, - error: result.reason, - }); - return { - integrationId: integration?.id ?? "unknown", - integrationName: integration?.name ?? "unknown", - integrationUrl: integration?.url ?? "", - systems: [], - updatedAt: new Date(0), - error: result.reason instanceof Error ? result.reason.message : String(result.reason), - }; - }); logger.debug("getSystems completed", { userId: ctx.session?.user?.id, integrationIds, @@ -91,8 +88,9 @@ export const beszelRouter = createTRPCRouter({ includeHistory: input.includeHistory, maxHistoryItems: input.maxHistoryItems, }); - const settled = await Promise.allSettled( - ctx.integrations.map(async (integration) => { + const results = await settleIntegrationQueries( + ctx.integrations, + async (integration) => { const alertsHandler = beszelAlertsRequestHandler.handler(integration, { includeHistory: input.includeHistory, maxHistoryItems: input.maxHistoryItems, @@ -118,26 +116,19 @@ export const beszelRouter = createTRPCRouter({ systemNameMap, updatedAt: alertsResult.timestamp, }; - }), + }, + { + fallback: (integration, error) => ({ + integrationId: integration.id, + integrationName: integration.name, + alerts: [], + history: [], + systemNameMap: {}, + updatedAt: new Date(0), + error: errorMessage(error), + }), + }, ); - const results = settled.map((result, index) => { - if (result.status === "fulfilled") return result.value; - const integration = ctx.integrations[index]; - logger.warn("getAlerts integration failed", { - userId: ctx.session?.user?.id, - integrationId: integration?.id, - error: result.reason, - }); - return { - integrationId: integration?.id ?? "unknown", - integrationName: integration?.name ?? "unknown", - alerts: [], - history: [], - systemNameMap: {}, - updatedAt: new Date(0), - error: result.reason instanceof Error ? result.reason.message : String(result.reason), - }; - }); logger.debug("getAlerts completed", { userId: ctx.session?.user?.id, integrationIds, @@ -200,7 +191,7 @@ export const beszelRouter = createTRPCRouter({ systemStats: [], containerStats: [], updatedAt: new Date(0), - error: error instanceof Error ? error.message : String(error), + error: errorMessage(error), }; } }), diff --git a/packages/api/src/settle-integrations.ts b/packages/api/src/settle-integrations.ts index 9ae1b8f04..643a3b949 100644 --- a/packages/api/src/settle-integrations.ts +++ b/packages/api/src/settle-integrations.ts @@ -9,9 +9,14 @@ interface IntegrationLike { kind: string; } +interface Options { + fallback?: (integration: TIntegration, error: unknown) => TResult; +} + export async function settleIntegrationQueries( integrations: TIntegration[], fn: (integration: TIntegration) => Promise, + options?: Options, ): Promise { const settled = await Promise.allSettled(integrations.map(async (integration) => fn(integration))); return settled.flatMap((result, index) => { @@ -24,6 +29,7 @@ export async function settleIntegrationQueries = { @@ -48,7 +47,7 @@ function UpsContent({ integrationIds, options, width }: UpsContentProps) { ); if (devices.length === 0) { - throw new NoIntegrationDataError(); + return ; } // Pick a layout from the available width so the widget stays useful at any size. The surrounding