From eecd37bf56eb1eef5d5b0855341985c279b1cd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zachary=20Gu=C3=A9not?= Date: Tue, 8 Sep 2026 15:27:35 +0200 Subject: [PATCH] feat(api): expose unifiOs widget summary query through tRPC router and request handler Create unifiOs request handler with 60s cache and stale-on-error fallback, and tRPC router exposing clientApi.widget.unifiOs.summary query. Include minimal widget stub for type system. Co-Authored-By: Claude Opus 5 --- packages/api/src/router/widgets/index.ts | 1 + packages/api/src/router/widgets/unifi-os.ts | 24 +++++++++++++++++++++ packages/request-handler/src/unifi-os.ts | 16 ++++++++++++++ packages/widgets/src/index.tsx | 2 ++ packages/widgets/src/unifi-os/component.tsx | 7 ++++++ packages/widgets/src/unifi-os/index.ts | 12 +++++++++++ 6 files changed, 62 insertions(+) create mode 100644 packages/api/src/router/widgets/unifi-os.ts create mode 100644 packages/request-handler/src/unifi-os.ts create mode 100644 packages/widgets/src/unifi-os/component.tsx create mode 100644 packages/widgets/src/unifi-os/index.ts diff --git a/packages/api/src/router/widgets/index.ts b/packages/api/src/router/widgets/index.ts index ba5987463..feebf1039 100644 --- a/packages/api/src/router/widgets/index.ts +++ b/packages/api/src/router/widgets/index.ts @@ -31,6 +31,7 @@ export const widgetRouter = createTRPCRouter({ options: lazy(() => import("./options").then((mod) => mod.optionsRouter)), releases: lazy(() => import("./releases").then((mod) => mod.releasesRouter)), networkController: lazy(() => import("./network-controller").then((mod) => mod.networkControllerRouter)), + unifiOs: lazy(() => import("./unifi-os").then((mod) => mod.unifiOsRouter)), firewall: lazy(() => import("./firewall").then((mod) => mod.firewallRouter)), notifications: lazy(() => import("./notifications").then((mod) => mod.notificationsRouter)), timetable: lazy(() => import("./timetable").then((mod) => mod.timetableRouter)), diff --git a/packages/api/src/router/widgets/unifi-os.ts b/packages/api/src/router/widgets/unifi-os.ts new file mode 100644 index 000000000..fe1c4d0cc --- /dev/null +++ b/packages/api/src/router/widgets/unifi-os.ts @@ -0,0 +1,24 @@ +import { unifiOsRequestHandler } from "@homarr/request-handler/unifi-os"; + +import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; +import { createTRPCRouter, publicProcedure } from "../../trpc"; + +export const unifiOsRouter = createTRPCRouter({ + summary: publicProcedure.concat(createManyIntegrationMiddleware("query", "unifiOs")).query(async ({ ctx }) => { + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = unifiOsRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); + + return { + integration: { + id: integration.id, + name: integration.name, + kind: integration.kind, + }, + summary: data, + updatedAt: timestamp, + }; + }); + }), +}); diff --git a/packages/request-handler/src/unifi-os.ts b/packages/request-handler/src/unifi-os.ts new file mode 100644 index 000000000..1696b948b --- /dev/null +++ b/packages/request-handler/src/unifi-os.ts @@ -0,0 +1,16 @@ +import { createIntegrationAsync } from "@homarr/integrations"; +import type { UnifiOsSummary } from "@homarr/integrations/types"; + +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; + +export const unifiOsRequestHandler = createIntegrationRequestHandler>({ + async requestAsync(integration, _input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getUnifiOsSummaryAsync(); + }, + cacheTtlMs: 60 * 1000, + // Une console momentanément injoignable doit afficher la dernière valeur connue + // plutôt qu'une tuile d'erreur ; le widget signale la péremption. + fallbackToStaleOnError: true, + staleIfErrorTtlMs: 10 * 60 * 1000, +}); diff --git a/packages/widgets/src/index.tsx b/packages/widgets/src/index.tsx index 36a9cbbfc..983fc6d76 100644 --- a/packages/widgets/src/index.tsx +++ b/packages/widgets/src/index.tsx @@ -38,6 +38,7 @@ import * as minecraftServerStatus from "./minecraft/server-status"; import * as networkControllerStatus from "./network-controller/network-status"; import * as networkControllerSummary from "./network-controller/summary"; import * as notebook from "./notebook"; +import * as unifiOs from "./unifi-os"; import * as paperlessNgx from "./paperless-ngx"; import * as patchmon from "./patchmon"; import * as notifications from "./notifications"; @@ -97,6 +98,7 @@ export const widgetImports = { mediaMissing, networkControllerSummary, networkControllerStatus, + unifiOs, rssFeed, bookmarks, bazarr, diff --git a/packages/widgets/src/unifi-os/component.tsx b/packages/widgets/src/unifi-os/component.tsx new file mode 100644 index 000000000..47eef7cae --- /dev/null +++ b/packages/widgets/src/unifi-os/component.tsx @@ -0,0 +1,7 @@ +"use client"; + +import type { WidgetComponentProps } from "../definition"; + +export default function UnifiOsWidgetComponent({ integrationIds }: WidgetComponentProps<"unifiOs">) { + return
UniFi OS Widget
; +} diff --git a/packages/widgets/src/unifi-os/index.ts b/packages/widgets/src/unifi-os/index.ts new file mode 100644 index 000000000..943d90388 --- /dev/null +++ b/packages/widgets/src/unifi-os/index.ts @@ -0,0 +1,12 @@ +import { IconWifi } from "@tabler/icons-react"; + +import { createWidgetDefinition } from "../definition"; +import { optionsBuilder } from "../options"; + +export const { definition, componentLoader } = createWidgetDefinition("unifiOs", { + icon: IconWifi, + createOptions() { + return optionsBuilder.from((factory) => ({})); + }, + supportedIntegrations: ["unifiOs"], +}).withDynamicImport(() => import("./component"));