diff --git a/packages/api/src/router/widgets/audio-stats.ts b/packages/api/src/router/widgets/audio-stats.ts index c70a56731..92b5a7e02 100644 --- a/packages/api/src/router/widgets/audio-stats.ts +++ b/packages/api/src/router/widgets/audio-stats.ts @@ -1,40 +1,26 @@ -import type { AudiobookshelfDashboardData, NavidromeDashboardData } from "@homarr/integrations/types"; import { audiobookshelfRequestHandler } from "@homarr/request-handler/audiobookshelf"; import { navidromeRequestHandler } from "@homarr/request-handler/navidrome"; import { createOneIntegrationMiddleware } from "../../middlewares/integration"; import { createTRPCRouter, publicProcedure } from "../../trpc"; -const requestHandlerByKind = { - navidrome: navidromeRequestHandler, - audiobookshelf: audiobookshelfRequestHandler, -} as const; - export const audioStatsRouter = createTRPCRouter({ getStats: publicProcedure .concat(createOneIntegrationMiddleware("query", "navidrome", "audiobookshelf")) .query(async ({ ctx }) => { - const fetchStatsByKind = { + const { kind } = ctx.integration; + + const fetchByKind = { navidrome: async () => { - const navidromeIntegration = { ...ctx.integration, kind: "navidrome" as const }; - const innerHandler = requestHandlerByKind.navidrome.handler(navidromeIntegration, {}); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - return { - kind: "navidrome" as const, - data: data.data as NavidromeDashboardData, - }; + const handler = navidromeRequestHandler.handler({ ...ctx.integration, kind: "navidrome" as const }, {}); + return (await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false })).data; }, audiobookshelf: async () => { - const audiobookshelfIntegration = { ...ctx.integration, kind: "audiobookshelf" as const }; - const innerHandler = requestHandlerByKind.audiobookshelf.handler(audiobookshelfIntegration, {}); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - return { - kind: "audiobookshelf" as const, - data: data.data as AudiobookshelfDashboardData, - }; + const handler = audiobookshelfRequestHandler.handler({ ...ctx.integration, kind: "audiobookshelf" as const }, {}); + return (await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false })).data; }, } as const; - return fetchStatsByKind[ctx.integration.kind](); + return { kind, data: await fetchByKind[kind]() }; }), }); diff --git a/packages/integrations/src/navidrome/navidrome-integration.ts b/packages/integrations/src/navidrome/navidrome-integration.ts index 706419a9d..b96a59293 100644 --- a/packages/integrations/src/navidrome/navidrome-integration.ts +++ b/packages/integrations/src/navidrome/navidrome-integration.ts @@ -19,17 +19,8 @@ const emptyLibraryMessage = "Library not found or empty"; const subsonicAuthErrorCodes = new Set([40, 41]); -const asArray = (value: TValue | TValue[] | undefined): TValue[] => { - if (value === undefined) { - return []; - } - - if (Array.isArray(value)) { - return value; - } - - return [value]; -}; +const asArray = (value: TValue | TValue[] | undefined): TValue[] => + [value].flat().filter((item): item is TValue => item !== undefined); export class NavidromeIntegration extends Integration { protected async testingAsync(input: IntegrationTestingInput): Promise { @@ -78,13 +69,10 @@ export class NavidromeIntegration extends Integration { private async getArtistCountAsync(): Promise { const response = await this.subsonicRequestAsync("/rest/getArtists.view", {}, { tolerateEmptyLibrary: true }); - let count = 0; - - for (const index of asArray(response.artists?.index)) { - count += asArray(index.artist).length; - } - - return count; + return asArray(response.artists?.index).reduce( + (count, index) => count + asArray(index.artist).length, + 0, + ); } private async getAlbumAndSongCountsAsync(): Promise<{ albumCount: number; songCount: number }> { diff --git a/packages/integrations/src/paperless-ngx/paperless-ngx-integration.ts b/packages/integrations/src/paperless-ngx/paperless-ngx-integration.ts index d4649a4b8..a6059c3e8 100644 --- a/packages/integrations/src/paperless-ngx/paperless-ngx-integration.ts +++ b/packages/integrations/src/paperless-ngx/paperless-ngx-integration.ts @@ -39,29 +39,23 @@ export class PaperlessNgxIntegration extends Integration { }; } - private async fetchStatisticsAsync() { - const url = this.url("/api/statistics/"); - const response = await fetchWithTrustedCertificatesAsync(url, { + private async fetchAuthenticatedAsync(path: `/${string}`) { + const response = await fetchWithTrustedCertificatesAsync(this.url(path), { headers: this.getAuthHeaders(), }); - if (!response.ok) { throw new ResponseError(response); } + return response; + } + private async fetchStatisticsAsync() { + const response = await this.fetchAuthenticatedAsync("/api/statistics/"); return paperlessNgxStatisticsSchema.parse(await response.json()); } private async fetchCountAsync(path: `/${string}`) { - const url = this.url(path); - const response = await fetchWithTrustedCertificatesAsync(url, { - headers: this.getAuthHeaders(), - }); - - if (!response.ok) { - throw new ResponseError(response); - } - + const response = await this.fetchAuthenticatedAsync(path); return paperlessNgxPaginatedCountSchema.parse(await response.json()).count; } diff --git a/packages/integrations/src/uptime-kuma/uptime-kuma-integration.ts b/packages/integrations/src/uptime-kuma/uptime-kuma-integration.ts index bad6a8479..e9c519a24 100644 --- a/packages/integrations/src/uptime-kuma/uptime-kuma-integration.ts +++ b/packages/integrations/src/uptime-kuma/uptime-kuma-integration.ts @@ -69,15 +69,9 @@ export class UptimeKumaIntegration extends Integration { const latestHeartbeat = heartbeats.at(-1); const uptimeKey = `${monitor.id}_24`; const uptimeRaw = heartbeat.uptimeList[uptimeKey]; - let uptimePercent24h: number | null = null; - if (typeof uptimeRaw === "number") { - uptimePercent24h = uptimeRaw * 100; - } - - let status: UptimeKumaMonitorCategory = "paused"; - if (latestHeartbeat) { - status = uptimeKumaHeartbeatCategoryMap[latestHeartbeat.status] ?? "down"; - } + const uptimePercent24h = typeof uptimeRaw === "number" ? uptimeRaw * 100 : null; + const status: UptimeKumaMonitorCategory = + (latestHeartbeat && (uptimeKumaHeartbeatCategoryMap[latestHeartbeat.status] ?? "down")) || "paused"; return { id: monitor.id, @@ -100,11 +94,8 @@ export class UptimeKumaIntegration extends Integration { .map((monitor) => monitor.uptimePercent24h) .filter((value): value is number => value !== null); - let averageUptimePercent = 0; - if (uptimeValues.length > 0) { - const totalUptime = uptimeValues.reduce((sum, value) => sum + value, 0); - averageUptimePercent = totalUptime / uptimeValues.length; - } + const averageUptimePercent = + uptimeValues.reduce((sum, value) => sum + value, 0) / Math.max(uptimeValues.length, 1); return { totalMonitors: monitors.length, @@ -116,24 +107,20 @@ export class UptimeKumaIntegration extends Integration { }; } - private async getStatusPageAsync() { - const response = await fetchWithTrustedCertificatesAsync(this.statusPageUrl()); - + private async fetchOkAsync(url: string | URL) { + const response = await fetchWithTrustedCertificatesAsync(url); if (!response.ok) { throw new ResponseError(response); } - return response; } - private async getHeartbeatAsync() { - const response = await fetchWithTrustedCertificatesAsync(this.heartbeatUrl()); + private getStatusPageAsync() { + return this.fetchOkAsync(this.statusPageUrl()); + } - if (!response.ok) { - throw new ResponseError(response); - } - - return response; + private getHeartbeatAsync() { + return this.fetchOkAsync(this.heartbeatUrl()); } private statusPageUrl() { diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index b34ebdb5b..8c7a6deca 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -3220,82 +3220,6 @@ "internalServerError": "Failed to fetch audio stats data" } }, - "audiobookshelf": { - "name": "Audiobookshelf", - "description": "Statistics from your Audiobookshelf instance", - "option": { - "showLibraryCount": { - "label": "Show library count", - "description": "Show the number of libraries on your instance" - }, - "showAudiobooks": { - "label": "Show audiobooks", - "description": "Show the total number of audiobooks across all libraries" - }, - "showPodcasts": { - "label": "Show podcasts", - "description": "Show the total number of podcasts across all libraries" - }, - "showListeningTime": { - "label": "Show listening time", - "description": "Show your total listening time" - }, - "showActiveSessions": { - "label": "Show active sessions", - "description": "Show the number of currently active playback sessions" - }, - "compactMode": { - "label": "Compact mode", - "description": "Use a denser layout with smaller tiles, ideal for small widget sizes" - } - }, - "libraryCount": "Libraries", - "audiobooks": "Audiobooks", - "podcasts": "Podcasts", - "listeningTime": "Listening Time", - "activeSessions": "Active Sessions", - "error": { - "internalServerError": "Failed to fetch Audiobookshelf data" - } - }, - "navidrome": { - "name": "Navidrome", - "description": "Statistics from your Navidrome music server", - "option": { - "showArtists": { - "label": "Show artists", - "description": "Show the total number of artists in your library" - }, - "showAlbums": { - "label": "Show albums", - "description": "Show the total number of albums in your library" - }, - "showSongs": { - "label": "Show songs", - "description": "Show the total number of songs in your library" - }, - "showNowPlaying": { - "label": "Show now playing", - "description": "Show tracks currently being played" - }, - "showNowPlayingList": { - "label": "Show now playing list", - "description": "Show individual tracks instead of only the active stream count" - }, - "maxNowPlayingItems": { - "label": "Max now playing items", - "description": "Maximum number of tracks to show in the now playing list" - } - }, - "artists": "Artists", - "albums": "Albums", - "songs": "Songs", - "nowPlaying": "Now Playing", - "nowPlayingCount": "{count, plural, one {# stream} other {# streams}}", - "error": { - "internalServerError": "Failed to fetch Navidrome data" - } - }, "systemResources": { "name": "System resources", "description": "CPU, Memory, Disk and other hardware usage of your system", diff --git a/packages/widgets/src/audio-stats/component.tsx b/packages/widgets/src/audio-stats/component.tsx index 84c673b9a..ff2b56863 100644 --- a/packages/widgets/src/audio-stats/component.tsx +++ b/packages/widgets/src/audio-stats/component.tsx @@ -4,29 +4,11 @@ import { clientApi } from "@homarr/api/client"; import type { WidgetComponentProps } from "../definition"; import { AudioStatsContent } from "./audio-stats-content"; -import type { AudioStatsDisplayOptions } from "./shared"; - -const defaultDisplayOptions: AudioStatsDisplayOptions = { - showArtists: true, - showAlbums: true, - showSongs: true, - showNowPlaying: true, - showNowPlayingList: true, - maxNowPlayingItems: 3, - showLibraryCount: true, - showAudiobooks: true, - showPodcasts: true, - showListeningTime: true, - showActiveSessions: true, - compactMode: false, -}; export default function AudioStatsWidget({ integrationIds, options, width }: WidgetComponentProps<"audioStats">) { const [response] = clientApi.widget.audioStats.getStats.useSuspenseQuery({ integrationId: integrationIds[0] ?? "", }); - const displayOptions = { ...defaultDisplayOptions, ...options }; - - return ; + return ; }