From c4a664b7ac6fcd821b216aad353da7ab897bd160 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Thu, 25 Jun 2026 11:25:01 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20bugs?= =?UTF-8?q?,=20dead=20code,=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical fixes: - Smart Home: invalidate entity state query after toggle mutation - Beszel live mode: use 5s refetchInterval instead of global 30s - Fix unit test assertions for 5-arg setQueryCacheAsync signature - Fix serialize/deserialize mismatch: JSON.parse on read (matches persister's JSON.stringify) - Remove render-time setActiveQueryCacheBoardId race (keep only useEffect) Error handling: - Log persistence errors instead of swallowing with catch(() => {}) - Log QueryCacheHydration failures with ErrorWithMetadata - Beszel: log partial failures (system details, container stats) Dead code removal: - Remove dead queryKey from all 39 request handlers + lib files - Delete unused Redis APIs: createCacheChannel, createChannelWithLatestAndEvents, createChannelEventHistory, history-channel.ts - Remove stale createCacheChannel mock from docker test Cleanup: - Seed system-resources chart from initial query data (no empty start) - Remove redundant refetchOnWindowFocus/refetchOnReconnect from BoardProvider --- .../query-cache-persister.ts | 9 +- .../[locale]/boards/(content)/_creator.tsx | 9 +- .../router/test/docker/docker-router.spec.ts | 10 -- packages/api/src/test/query-cache.spec.ts | 2 + packages/boards/src/context.tsx | 8 +- packages/redis/src/index.ts | 5 - packages/redis/src/lib/channel.ts | 146 ------------------ .../redis/src/lib/channels/history-channel.ts | 6 - packages/request-handler/src/anchor-notes.ts | 2 - .../src/archive-team-warrior.ts | 1 - .../request-handler/src/audiobookshelf.ts | 1 - packages/request-handler/src/beszel.ts | 13 +- packages/request-handler/src/calendar.ts | 1 - packages/request-handler/src/coolify.ts | 1 - packages/request-handler/src/dns-hole.ts | 1 - packages/request-handler/src/docker.ts | 1 - packages/request-handler/src/downloads.ts | 1 - .../request-handler/src/duckduckgo-bangs.ts | 1 - packages/request-handler/src/firewall.ts | 4 - .../request-handler/src/health-monitoring.ts | 2 - packages/request-handler/src/immich.ts | 3 - .../request-handler/src/indexer-manager.ts | 1 - .../src/lib/integration-request-handler.ts | 2 - .../src/lib/request-handler.ts | 1 - .../src/lib/widget-request-handler.ts | 2 - packages/request-handler/src/media-release.ts | 1 - .../request-handler/src/media-request-list.ts | 1 - .../src/media-request-stats.ts | 1 - packages/request-handler/src/media-server.ts | 1 - .../request-handler/src/media-transcoding.ts | 1 - .../src/minecraft-server-status.ts | 1 - packages/request-handler/src/navidrome.ts | 1 - .../request-handler/src/network-controller.ts | 1 - packages/request-handler/src/notifications.ts | 1 - packages/request-handler/src/paperless-ngx.ts | 1 - packages/request-handler/src/releases.ts | 1 - packages/request-handler/src/rss-feeds.ts | 1 - .../src/smart-home-entity-state.ts | 1 - .../request-handler/src/speedtest-tracker.ts | 1 - packages/request-handler/src/stock-price.ts | 1 - packages/request-handler/src/timetable.ts | 2 - packages/request-handler/src/tracearr.ts | 1 - packages/request-handler/src/umami.ts | 6 - .../request-handler/src/update-checker.ts | 1 - packages/request-handler/src/ups.ts | 1 - packages/request-handler/src/uptime-kuma.ts | 1 - packages/request-handler/src/weather.ts | 1 - .../src/beszel/_shared/use-live-stats.ts | 17 +- .../src/smart-home/entity-state/component.tsx | 5 +- .../src/system-resources/component.tsx | 2 +- 50 files changed, 31 insertions(+), 254 deletions(-) delete mode 100644 packages/redis/src/lib/channels/history-channel.ts diff --git a/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts b/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts index 6e72f26e3..7a36e611a 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts +++ b/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts @@ -3,17 +3,22 @@ import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persi import { fetchApi } from "@homarr/api/client"; import { getActiveQueryCacheBoardId, queryCacheStoragePrefix } from "@homarr/api/query-cache"; +// ponytail: write-only persister — restore is SSR-only via QueryCacheHydration const queryCacheStorage = { getItem: (_key: string) => null as string | null, async setItem(_key: string, value: string) { const boardId = getActiveQueryCacheBoardId(); if (!boardId) return; - await fetchApi.queryCache.setItem.mutate({ boardId, value }).catch(() => {}); + await fetchApi.queryCache.setItem.mutate({ boardId, value }).catch((error) => { + console.warn("[query-cache] persist failed", error); + }); }, async removeItem(_key: string) { const boardId = getActiveQueryCacheBoardId(); if (!boardId) return; - await fetchApi.queryCache.removeItem.mutate({ boardId }).catch(() => {}); + await fetchApi.queryCache.removeItem.mutate({ boardId }).catch((error) => { + console.warn("[query-cache] remove failed", error); + }); }, }; diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx index ad16c2e92..49ded33c2 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx @@ -7,8 +7,6 @@ import "~/styles/gridstack.scss"; import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; import type { PersistedClient } from "@tanstack/react-query-persist-client"; -import { parse } from "superjson"; - import { getQueryClient } from "@homarr/api/server"; import { IntegrationProvider } from "@homarr/auth/client"; import { auth } from "@homarr/auth/next"; @@ -123,11 +121,14 @@ async function QueryCacheHydration({ userId, boardId }: { userId: string; boardI const serialized = await getQueryCacheAsync(userId, boardId); if (!serialized) return null; - const persisted = parse(serialized); + const persisted = JSON.parse(serialized) as PersistedClient; if (!persisted?.clientState?.queries?.length) return null; return ; - } catch { + } catch (error) { + logger.warn( + new ErrorWithMetadata("Failed to hydrate query cache", { userId, boardId }, { cause: error }), + ); return null; } } diff --git a/packages/api/src/router/test/docker/docker-router.spec.ts b/packages/api/src/router/test/docker/docker-router.spec.ts index a40f5e4dd..4a5a32a88 100644 --- a/packages/api/src/router/test/docker/docker-router.spec.ts +++ b/packages/api/src/router/test/docker/docker-router.spec.ts @@ -29,16 +29,6 @@ vi.mock("@homarr/request-handler/docker", () => ({ }), }, })); -vi.mock("@homarr/redis", () => ({ - createCacheChannel: () => ({ - consumeAsync: async () => ({ - timestamp: new Date().toISOString(), - data: { containers: [] }, - }), - invalidateAsync: async () => {}, - }), -})); - vi.mock("@homarr/docker/env", () => ({ env: { ENABLE_DOCKER: true, diff --git a/packages/api/src/test/query-cache.spec.ts b/packages/api/src/test/query-cache.spec.ts index 0b119c28f..090544cb2 100644 --- a/packages/api/src/test/query-cache.spec.ts +++ b/packages/api/src/test/query-cache.spec.ts @@ -92,6 +92,7 @@ describe("queryCacheRouter", () => { "board-1", "cached-value", expect.any(Number), + queryCacheMaxValueBytes, ); await caller.removeItem({ boardId: "board-1" }); @@ -124,6 +125,7 @@ describe("queryCacheRouter", () => { "board-1", "cached-value", expect.any(Number), + queryCacheMaxValueBytes, ); }); diff --git a/packages/boards/src/context.tsx b/packages/boards/src/context.tsx index c2ccc690a..7eba66f3f 100644 --- a/packages/boards/src/context.tsx +++ b/packages/boards/src/context.tsx @@ -22,15 +22,9 @@ export const BoardProvider = ({ }>) => { const { data } = clientApi.board.getBoardByName.useQuery( { name: initialBoard.name }, - { - initialData: initialBoard, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, + { initialData: initialBoard }, ); - setActiveQueryCacheBoardId(data.id); - // Update the board name so it can be used within updateBoard method updateBoardName(initialBoard.name); diff --git a/packages/redis/src/index.ts b/packages/redis/src/index.ts index 4186948ce..830335fd8 100644 --- a/packages/redis/src/index.ts +++ b/packages/redis/src/index.ts @@ -3,9 +3,6 @@ import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; import { createListChannel, createQueueChannel, createSubPubChannel } from "./lib/channel"; export { - createCacheChannel, - createChannelWithLatestAndEvents, - createChannelEventHistory, handshakeAsync, createSubPubChannel, createGetSetChannel, @@ -15,8 +12,6 @@ export { invalidateIntegrationCacheAsync, } from "./lib/channel"; -export { createIntegrationHistoryChannel } from "./lib/channels/history-channel"; - export const exampleChannel = createSubPubChannel<{ message: string }>("example"); export const pingChannel = createSubPubChannel< { url: string; statusCode: number; durationMs: number } | { url: string; error: string } diff --git a/packages/redis/src/lib/channel.ts b/packages/redis/src/lib/channel.ts index 7cb84be68..506d88b85 100644 --- a/packages/redis/src/lib/channel.ts +++ b/packages/redis/src/lib/channel.ts @@ -156,80 +156,6 @@ export const removeQueryCacheAsync = async (userId: string, boardId: string) => await getSetClient.del(queryCacheKey(userId, boardId)); }; -/** - * Creates a new cache channel. - * @param name name of the channel - * @param cacheDurationMs duration in milliseconds to cache - * @returns cache channel object - */ -export const createCacheChannel = (name: string, cacheDurationMs: number = 5 * 60 * 1000) => { - const cacheChannelName = `cache:${name}`; - - return { - /** - * Get the data from the cache channel. - * @returns data or null if not found or expired - */ - getAsync: async () => { - const data = await getSetClient.get(cacheChannelName); - if (!data) return null; - - const parsedData = superjson.parse<{ data: TData; timestamp: Date }>(data); - const now = new Date(); - const diff = now.getTime() - parsedData.timestamp.getTime(); - if (diff > cacheDurationMs) return null; - - return parsedData; - }, - /** - * Consume the data from the cache channel, if not present or expired, it will call the callback to get new data. - * @param callback callback function to get new data if not present or expired - * @returns data or new data if not present or expired - */ - consumeAsync: async (callback: () => Promise) => { - const data = await getSetClient.get(cacheChannelName); - - const getNewDataAsync = async () => { - logger.debug(`Cache miss for channel '${cacheChannelName}'`); - const newData = await callback(); - const result = { data: newData, timestamp: new Date() }; - await getSetClient.set(cacheChannelName, superjson.stringify(result)); - logger.debug(`Cache updated for channel '${cacheChannelName}'`); - return result; - }; - - if (!data) { - return await getNewDataAsync(); - } - - const parsedData = superjson.parse<{ data: TData; timestamp: Date }>(data); - const now = new Date(); - const diff = now.getTime() - parsedData.timestamp.getTime(); - - if (diff > cacheDurationMs) { - return await getNewDataAsync(); - } - - logger.debug(`Cache hit for channel '${cacheChannelName}'`); - - return parsedData; - }, - /** - * Invalidate the cache channels data. - */ - invalidateAsync: async () => { - await getSetClient.del(cacheChannelName); - }, - /** - * Set the data in the cache channel. - * @param data data to be stored in the cache channel - */ - setAsync: async (data: TData) => { - await getSetClient.set(cacheChannelName, superjson.stringify({ data, timestamp: new Date() })); - }, - }; -}; - /** * Invalidates all cached data for a given integration by deleting every Redis key * that starts with `integration:${integrationId}:` as well as the integration's @@ -260,44 +186,6 @@ export const invalidateIntegrationCacheAsync = async (integrationId: string): Pr } }; -export const createChannelEventHistory = (channelName: string, maxElements = 32) => { - return { - subscribe: (callback: (data: TData) => void) => { - return ChannelSubscriptionTracker.subscribe(channelName, (message) => { - callback(superjson.parse(message)); - }); - }, - pushAsync: async (data: TData, options = { publish: false }) => { - if (options.publish) await publisher.publish(channelName, superjson.stringify(data)); - await getSetClient.lpush(channelName, superjson.stringify({ data, timestamp: new Date() })); - await getSetClient.ltrim(channelName, 0, maxElements); - }, - clearAsync: async () => { - await getSetClient.del(channelName); - }, - /** - * Returns a slice of the available data in the channel. - * If any of the indexes are out of range (or -range), returned data will be clamped. - * @param startIndex Start index of the slice, negative values are counted from the end, defaults at beginning of range. - * @param endIndex End index of the slice, negative values are counted from the end, defaults at end of range. - */ - getSliceAsync: async (startIndex = 0, endIndex = -1) => { - const range = await getSetClient.lrange(channelName, startIndex, endIndex); - return range.map((item) => superjson.parse<{ data: TData; timestamp: Date }>(item)); - }, - getSliceUntilTimeAsync: async (time: Date) => { - const itemsInCollection = await getSetClient.lrange(channelName, 0, -1); - return itemsInCollection - .map((item) => superjson.parse<{ data: TData; timestamp: Date }>(item)) - .filter((item) => item.timestamp < time); - }, - getLengthAsync: async () => { - return await getSetClient.llen(channelName); - }, - name: channelName, - }; -}; - /** * @deprecated This function should no longer be used, see history-channel functions. */ @@ -362,40 +250,6 @@ export const createChannelEventHistoryOld = (channelName: string, maxElem }; }; -export const createChannelWithLatestAndEvents = (channelName: string) => { - return { - subscribe: (callback: (data: TData) => void) => { - return ChannelSubscriptionTracker.subscribe(channelName, (message) => { - callback(superjson.parse(message)); - }); - }, - publishAndUpdateLastStateAsync: async (data: TData) => { - // Set first, then publish. If publish fails the storage is still consistent - // and subscribers will catch up on the next event. - const payload = superjson.stringify({ data, timestamp: new Date() }); - await getSetClient.set(channelName, payload); - try { - await publisher.publish(channelName, superjson.stringify(data)); - } catch (error) { - logger.warn("Failed to publish cache update (storage is correct, subscribers will catch up)", { - channel: channelName, - error: error instanceof Error ? error.message : String(error), - }); - } - }, - setAsync: async (data: TData) => { - await getSetClient.set(channelName, superjson.stringify({ data, timestamp: new Date() })); - }, - getAsync: async () => { - const data = await getSetClient.get(channelName); - if (!data) return null; - - return superjson.parse<{ data: TData; timestamp: Date }>(data); - }, - name: channelName, - }; -}; - const queueClient = createRedisConnection(); type WithId = TItem & { _id: string }; diff --git a/packages/redis/src/lib/channels/history-channel.ts b/packages/redis/src/lib/channels/history-channel.ts deleted file mode 100644 index 541830464..000000000 --- a/packages/redis/src/lib/channels/history-channel.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createChannelEventHistory } from "../channel"; - -export const createIntegrationHistoryChannel = (integrationId: string, queryKey: string, maxElements = 32) => { - const channelName = `integration:${integrationId}:history:${queryKey}`; - return createChannelEventHistory(channelName, maxElements); -}; diff --git a/packages/request-handler/src/anchor-notes.ts b/packages/request-handler/src/anchor-notes.ts index b821d23f0..75677b8ba 100644 --- a/packages/request-handler/src/anchor-notes.ts +++ b/packages/request-handler/src/anchor-notes.ts @@ -8,7 +8,6 @@ export const anchorNotesListRequestHandler = createIntegrationRequestHandler< "anchor", AnchorNotesListInput >({ - queryKey: "anchorNotesList", async requestAsync(integration, input) { const instance = await createIntegrationAsync(integration); return instance.listNotesAsync(input); @@ -16,7 +15,6 @@ export const anchorNotesListRequestHandler = createIntegrationRequestHandler< }); export const anchorNoteRequestHandler = createIntegrationRequestHandler({ - queryKey: "anchorNote", async requestAsync(integration, input) { const instance = await createIntegrationAsync(integration); return instance.getNoteAsync(input.noteId); diff --git a/packages/request-handler/src/archive-team-warrior.ts b/packages/request-handler/src/archive-team-warrior.ts index bf3c80f15..e02391f19 100644 --- a/packages/request-handler/src/archive-team-warrior.ts +++ b/packages/request-handler/src/archive-team-warrior.ts @@ -13,5 +13,4 @@ export const archiveTeamWarriorRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getStatusAsync(); }, - queryKey: "archiveTeamWarriorStatus", }); diff --git a/packages/request-handler/src/audiobookshelf.ts b/packages/request-handler/src/audiobookshelf.ts index 54f4003d8..12562342c 100644 --- a/packages/request-handler/src/audiobookshelf.ts +++ b/packages/request-handler/src/audiobookshelf.ts @@ -12,5 +12,4 @@ export const audiobookshelfRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - queryKey: "audiobookshelfDashboard", }); diff --git a/packages/request-handler/src/beszel.ts b/packages/request-handler/src/beszel.ts index 449df7b9b..4a60a3a3a 100644 --- a/packages/request-handler/src/beszel.ts +++ b/packages/request-handler/src/beszel.ts @@ -62,7 +62,10 @@ export const beszelSystemsRequestHandler = createIntegrationRequestHandler< const systems = await instance.getSystemsAsync(); const enriched = await Promise.all( systems.map(async (system) => { - const details = await instance.getSystemDetailsAsync(system.id).catch(() => null); + const details = await instance.getSystemDetailsAsync(system.id).catch((error) => { + logger.warn("Failed to fetch Beszel system details", { systemId: system.id, error: String(error) }); + return null; + }); return mapToSystemRow(system, details); }), ); @@ -73,7 +76,6 @@ export const beszelSystemsRequestHandler = createIntegrationRequestHandler< }); return enriched; }, - queryKey: "beszelSystems", }); export interface BeszelAlertsData { @@ -99,7 +101,6 @@ export const beszelAlertsRequestHandler = createIntegrationRequestHandler< }); return { alerts, history }; }, - queryKey: "beszelAlerts", }); const timePeriodConfig: Record = { @@ -127,7 +128,10 @@ export const beszelStatsRequestHandler = createIntegrationRequestHandler< 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(() => []) + ? await instance.getContainerStatsAsync(input.systemId, config.type, config.perPage).catch((error) => { + logger.warn("Failed to fetch Beszel container stats", { systemId: input.systemId, error: String(error) }); + return []; + }) : []; logger.debug("beszelStats fetch completed", { integrationId: integration.id, @@ -139,5 +143,4 @@ export const beszelStatsRequestHandler = createIntegrationRequestHandler< }); return { systemStats, containerStats }; }, - queryKey: "beszelStats", }); diff --git a/packages/request-handler/src/calendar.ts b/packages/request-handler/src/calendar.ts index d3ff2414f..a358d13e8 100644 --- a/packages/request-handler/src/calendar.ts +++ b/packages/request-handler/src/calendar.ts @@ -23,5 +23,4 @@ export const calendarMonthRequestHandler = createIntegrationRequestHandler< input.showUnmonitored, ); }, - queryKey: "calendarMonth", }); diff --git a/packages/request-handler/src/coolify.ts b/packages/request-handler/src/coolify.ts index c129299c6..25ddfe962 100644 --- a/packages/request-handler/src/coolify.ts +++ b/packages/request-handler/src/coolify.ts @@ -12,5 +12,4 @@ export const coolifyRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getInstanceInfoAsync(); }, - queryKey: "coolifyInfo", }); diff --git a/packages/request-handler/src/dns-hole.ts b/packages/request-handler/src/dns-hole.ts index 95bfbbd59..85188c740 100644 --- a/packages/request-handler/src/dns-hole.ts +++ b/packages/request-handler/src/dns-hole.ts @@ -13,5 +13,4 @@ export const dnsHoleRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getSummaryAsync(); }, - queryKey: "dnsHoleSummary", }); diff --git a/packages/request-handler/src/docker.ts b/packages/request-handler/src/docker.ts index 4b891e952..12a78c05a 100644 --- a/packages/request-handler/src/docker.ts +++ b/packages/request-handler/src/docker.ts @@ -171,7 +171,6 @@ const mockContainers: { ]; export const dockerContainersRequestHandler = createWidgetRequestHandler({ - queryKey: "dockerContainersResult", async requestAsync() { if (isDemoMode) { return mockContainers; diff --git a/packages/request-handler/src/downloads.ts b/packages/request-handler/src/downloads.ts index 721ef7a4a..12106b07a 100644 --- a/packages/request-handler/src/downloads.ts +++ b/packages/request-handler/src/downloads.ts @@ -13,5 +13,4 @@ export const downloadClientRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getClientJobsAndStatusAsync(input); }, - queryKey: "downloadClientJobStatus", }); diff --git a/packages/request-handler/src/duckduckgo-bangs.ts b/packages/request-handler/src/duckduckgo-bangs.ts index 8b2bd4877..628e125a1 100644 --- a/packages/request-handler/src/duckduckgo-bangs.ts +++ b/packages/request-handler/src/duckduckgo-bangs.ts @@ -37,7 +37,6 @@ const normalizeBangToken = (token: string) => token.toLowerCase().trim(); const compareBangTokenAsc = (left: DuckDuckGoBang, right: DuckDuckGoBang) => left.t.localeCompare(right.t); export const duckDuckGoBangsRequestHandler = createRequestHandler({ - queryKey: "duckduckgo-bangs", async requestAsync(_) { if (env.NO_EXTERNAL_CONNECTION) { return []; diff --git a/packages/request-handler/src/firewall.ts b/packages/request-handler/src/firewall.ts index 3a4018b00..ad4f25b70 100644 --- a/packages/request-handler/src/firewall.ts +++ b/packages/request-handler/src/firewall.ts @@ -18,7 +18,6 @@ export const firewallCpuRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return integrationInstance.getFirewallCpuAsync(); }, - queryKey: "firewallCpuSummary", }); export const firewallMemoryRequestHandler = createIntegrationRequestHandler< @@ -30,7 +29,6 @@ export const firewallMemoryRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getFirewallMemoryAsync(); }, - queryKey: "firewallMemorySummary", }); export const firewallInterfacesRequestHandler = createIntegrationRequestHandler< @@ -42,7 +40,6 @@ export const firewallInterfacesRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getFirewallInterfacesAsync(); }, - queryKey: "firewallInterfacesSummary", }); export const firewallVersionRequestHandler = createIntegrationRequestHandler< @@ -54,5 +51,4 @@ export const firewallVersionRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getFirewallVersionAsync(); }, - queryKey: "firewallVersionSummary", }); diff --git a/packages/request-handler/src/health-monitoring.ts b/packages/request-handler/src/health-monitoring.ts index b0913afb8..604fff9da 100644 --- a/packages/request-handler/src/health-monitoring.ts +++ b/packages/request-handler/src/health-monitoring.ts @@ -13,7 +13,6 @@ export const systemInfoRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getSystemInfoAsync(); }, - queryKey: "systemInfo", }); export const clusterInfoRequestHandler = createIntegrationRequestHandler< @@ -25,5 +24,4 @@ export const clusterInfoRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getClusterInfoAsync(); }, - queryKey: "clusterInfo", }); diff --git a/packages/request-handler/src/immich.ts b/packages/request-handler/src/immich.ts index 6e296d8d6..56fc89bd0 100644 --- a/packages/request-handler/src/immich.ts +++ b/packages/request-handler/src/immich.ts @@ -13,7 +13,6 @@ export const immichStatsRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getServerStatsAsync(); }, - queryKey: "immich-server-stats", }); export const immichAlbumsRequestHandler = createIntegrationRequestHandler< @@ -29,7 +28,6 @@ export const immichAlbumsRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getAlbumsAsync(); }, - queryKey: "immich-albums", }); export const immichAlbumRequestHandler = createIntegrationRequestHandler< @@ -41,5 +39,4 @@ export const immichAlbumRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getAlbumAsync(input.albumId); }, - queryKey: "immich-album", }); diff --git a/packages/request-handler/src/indexer-manager.ts b/packages/request-handler/src/indexer-manager.ts index b003bf31f..f21a61f8f 100644 --- a/packages/request-handler/src/indexer-manager.ts +++ b/packages/request-handler/src/indexer-manager.ts @@ -13,5 +13,4 @@ export const indexerManagerRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getIndexersAsync(); }, - queryKey: "indexerManager", }); diff --git a/packages/request-handler/src/lib/integration-request-handler.ts b/packages/request-handler/src/lib/integration-request-handler.ts index 8bffad266..e2c0795ad 100644 --- a/packages/request-handler/src/lib/integration-request-handler.ts +++ b/packages/request-handler/src/lib/integration-request-handler.ts @@ -11,7 +11,6 @@ type IntegrationOfKind = Omit> { - queryKey: string; requestAsync: (integration: IntegrationOfKind, input: TInput) => Promise; } @@ -24,7 +23,6 @@ export const createIntegrationRequestHandler = < ) => ({ handler: (integration: IntegrationOfKind, itemOptions: TInput) => createRequestHandler }>({ - queryKey: options.queryKey, requestAsync: async (input) => options.requestAsync(input.integration, input.options), }).handler({ options: itemOptions, integration }), }); diff --git a/packages/request-handler/src/lib/request-handler.ts b/packages/request-handler/src/lib/request-handler.ts index 124f4d5e8..020e3c4d9 100644 --- a/packages/request-handler/src/lib/request-handler.ts +++ b/packages/request-handler/src/lib/request-handler.ts @@ -1,5 +1,4 @@ interface Options> { - queryKey: string; requestAsync: (input: TInput) => Promise; } diff --git a/packages/request-handler/src/lib/widget-request-handler.ts b/packages/request-handler/src/lib/widget-request-handler.ts index aff397ee0..8649791af 100644 --- a/packages/request-handler/src/lib/widget-request-handler.ts +++ b/packages/request-handler/src/lib/widget-request-handler.ts @@ -1,7 +1,6 @@ import { createRequestHandler } from "./request-handler"; interface Options> { - queryKey: string; requestAsync: (input: TInput) => Promise; } @@ -10,7 +9,6 @@ export const createWidgetRequestHandler = ({ handler: (widgetOptions: TInput) => createRequestHandler({ - queryKey: options.queryKey, requestAsync: options.requestAsync, }).handler(widgetOptions), }); diff --git a/packages/request-handler/src/media-release.ts b/packages/request-handler/src/media-release.ts index 5bc5f9843..02209f68f 100644 --- a/packages/request-handler/src/media-release.ts +++ b/packages/request-handler/src/media-release.ts @@ -13,5 +13,4 @@ export const mediaReleaseRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getMediaReleasesAsync(); }, - queryKey: "mediaReleases", }); diff --git a/packages/request-handler/src/media-request-list.ts b/packages/request-handler/src/media-request-list.ts index 0f337c359..62cf29a18 100644 --- a/packages/request-handler/src/media-request-list.ts +++ b/packages/request-handler/src/media-request-list.ts @@ -13,5 +13,4 @@ export const mediaRequestListRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getRequestsAsync(); }, - queryKey: "mediaRequestList", }); diff --git a/packages/request-handler/src/media-request-stats.ts b/packages/request-handler/src/media-request-stats.ts index 699263eef..61ff810b8 100644 --- a/packages/request-handler/src/media-request-stats.ts +++ b/packages/request-handler/src/media-request-stats.ts @@ -16,5 +16,4 @@ export const mediaRequestStatsRequestHandler = createIntegrationRequestHandler< users: await integrationInstance.getUsersAsync(), }; }, - queryKey: "mediaRequestStats", }); diff --git a/packages/request-handler/src/media-server.ts b/packages/request-handler/src/media-server.ts index 14bb8aae5..55a7850e0 100644 --- a/packages/request-handler/src/media-server.ts +++ b/packages/request-handler/src/media-server.ts @@ -15,5 +15,4 @@ export const mediaServerRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getCurrentSessionsAsync({ showOnlyPlaying: input.showOnlyPlaying }); }, - queryKey: "mediaServerSessions", }); diff --git a/packages/request-handler/src/media-transcoding.ts b/packages/request-handler/src/media-transcoding.ts index 49c406e04..ba6455ccd 100644 --- a/packages/request-handler/src/media-transcoding.ts +++ b/packages/request-handler/src/media-transcoding.ts @@ -9,7 +9,6 @@ export const mediaTranscodingRequestHandler = createIntegrationRequestHandler< IntegrationKindByCategory<"mediaTranscoding">, { pageOffset: number; pageSize: number } >({ - queryKey: "mediaTranscoding", async requestAsync(integration, input) { const integrationInstance = await createIntegrationAsync(integration); return { diff --git a/packages/request-handler/src/minecraft-server-status.ts b/packages/request-handler/src/minecraft-server-status.ts index 33ba259a4..664a0a7f3 100644 --- a/packages/request-handler/src/minecraft-server-status.ts +++ b/packages/request-handler/src/minecraft-server-status.ts @@ -6,7 +6,6 @@ import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import { createWidgetRequestHandler } from "./lib/widget-request-handler"; export const minecraftServerStatusRequestHandler = createWidgetRequestHandler({ - queryKey: "minecraftServerStatusApiResult", async requestAsync(input: { domain: string; isBedrockServer: boolean }) { const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`; diff --git a/packages/request-handler/src/navidrome.ts b/packages/request-handler/src/navidrome.ts index b83f5be4e..0ad39ed9a 100644 --- a/packages/request-handler/src/navidrome.ts +++ b/packages/request-handler/src/navidrome.ts @@ -12,5 +12,4 @@ export const navidromeRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - queryKey: "navidromeDashboard", }); diff --git a/packages/request-handler/src/network-controller.ts b/packages/request-handler/src/network-controller.ts index a4351f4d2..123261025 100644 --- a/packages/request-handler/src/network-controller.ts +++ b/packages/request-handler/src/network-controller.ts @@ -13,5 +13,4 @@ export const networkControllerRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getNetworkSummaryAsync(); }, - queryKey: "networkControllerSummary", }); diff --git a/packages/request-handler/src/notifications.ts b/packages/request-handler/src/notifications.ts index 8821f0bf1..479ff81fa 100644 --- a/packages/request-handler/src/notifications.ts +++ b/packages/request-handler/src/notifications.ts @@ -13,5 +13,4 @@ export const notificationsRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getNotificationsAsync(); }, - queryKey: "notificationsJobStatus", }); diff --git a/packages/request-handler/src/paperless-ngx.ts b/packages/request-handler/src/paperless-ngx.ts index a9b175a38..e6b088006 100644 --- a/packages/request-handler/src/paperless-ngx.ts +++ b/packages/request-handler/src/paperless-ngx.ts @@ -12,5 +12,4 @@ export const paperlessNgxStatsRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getStatsAsync(); }, - queryKey: "paperless-ngx-stats", }); diff --git a/packages/request-handler/src/releases.ts b/packages/request-handler/src/releases.ts index 149bb4954..8b67e9793 100644 --- a/packages/request-handler/src/releases.ts +++ b/packages/request-handler/src/releases.ts @@ -13,5 +13,4 @@ export const releasesRequestHandler = createIntegrationRequestHandler< const instance = await createIntegrationAsync(integration); return instance.getLatestMatchingReleaseAsync(input.identifier, input.versionRegex); }, - queryKey: "repositoriesReleases", }); diff --git a/packages/request-handler/src/rss-feeds.ts b/packages/request-handler/src/rss-feeds.ts index 3098fdb1d..0597a325f 100644 --- a/packages/request-handler/src/rss-feeds.ts +++ b/packages/request-handler/src/rss-feeds.ts @@ -10,7 +10,6 @@ import { createWidgetRequestHandler } from "./lib/widget-request-handler"; const logger = createLogger({ module: "rssFeedsRequestHandler" }); export const rssFeedsRequestHandler = createWidgetRequestHandler({ - queryKey: "rssFeedList", async requestAsync(input: { url: string; count: number }) { const result = (await extract(input.url, { getExtraEntryFields: (feedEntry) => { diff --git a/packages/request-handler/src/smart-home-entity-state.ts b/packages/request-handler/src/smart-home-entity-state.ts index 097dec583..3c8851522 100644 --- a/packages/request-handler/src/smart-home-entity-state.ts +++ b/packages/request-handler/src/smart-home-entity-state.ts @@ -18,5 +18,4 @@ export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandle return result.data.state; }, - queryKey: "smartHome-entityState", }); diff --git a/packages/request-handler/src/speedtest-tracker.ts b/packages/request-handler/src/speedtest-tracker.ts index a197fce9a..3f03f7187 100644 --- a/packages/request-handler/src/speedtest-tracker.ts +++ b/packages/request-handler/src/speedtest-tracker.ts @@ -12,5 +12,4 @@ export const speedtestTrackerRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - queryKey: "speedtestTrackerDashboard", }); diff --git a/packages/request-handler/src/stock-price.ts b/packages/request-handler/src/stock-price.ts index 2e382828e..177b223aa 100644 --- a/packages/request-handler/src/stock-price.ts +++ b/packages/request-handler/src/stock-price.ts @@ -6,7 +6,6 @@ import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import { createWidgetRequestHandler } from "./lib/widget-request-handler"; export const fetchStockPriceHandler = createWidgetRequestHandler({ - queryKey: "fetchStockPriceResult", async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) { const response = await withTimeoutAsync(async (signal) => { return await fetchWithTrustedCertificatesAsync( diff --git a/packages/request-handler/src/timetable.ts b/packages/request-handler/src/timetable.ts index b020829f3..10050a89e 100644 --- a/packages/request-handler/src/timetable.ts +++ b/packages/request-handler/src/timetable.ts @@ -13,7 +13,6 @@ export const timetableSearchStationsRequestHandler = createIntegrationRequestHan const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.searchStationsAsync(input.query); }, - queryKey: "timetableSearchStations", }); export const timetableGetTimetableRequestHandler = createIntegrationRequestHandler< @@ -28,5 +27,4 @@ export const timetableGetTimetableRequestHandler = createIntegrationRequestHandl limit: input.limit, }); }, - queryKey: "timetableGetTimetable", }); diff --git a/packages/request-handler/src/tracearr.ts b/packages/request-handler/src/tracearr.ts index 1cb0ae91f..641bf2922 100644 --- a/packages/request-handler/src/tracearr.ts +++ b/packages/request-handler/src/tracearr.ts @@ -12,5 +12,4 @@ export const tracearrRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - queryKey: "tracearrDashboard", }); diff --git a/packages/request-handler/src/umami.ts b/packages/request-handler/src/umami.ts index 8d9a40141..02ef71140 100644 --- a/packages/request-handler/src/umami.ts +++ b/packages/request-handler/src/umami.ts @@ -12,7 +12,6 @@ export const umamiRequestHandler = createIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getVisitorStatsAsync(input.websiteId, input.timeFrame, input.eventName); }, - queryKey: "umamiVisitorStats", }); export const umamiEventNamesRequestHandler = createIntegrationRequestHandler({ @@ -20,7 +19,6 @@ export const umamiEventNamesRequestHandler = createIntegrationRequestHandler { return await fetchWithTrustedCertificatesAsync( diff --git a/packages/widgets/src/beszel/_shared/use-live-stats.ts b/packages/widgets/src/beszel/_shared/use-live-stats.ts index 4a06d11da..b07bbbd2c 100644 --- a/packages/widgets/src/beszel/_shared/use-live-stats.ts +++ b/packages/widgets/src/beszel/_shared/use-live-stats.ts @@ -1,24 +1,13 @@ "use client"; -import { useMemo } from "react"; - import { clientApi } from "@homarr/api/client"; export const useLiveStats = (integrationIds: string[], systemId: string, enabled: boolean) => { - const queryInput = useMemo( - () => ({ - integrationIds, - systemId, - timePeriod: "1m" as const, - includeDocker: true, - }), - [integrationIds, systemId], + const { data, error } = clientApi.widget.beszel.getSystemStats.useQuery( + { integrationIds, systemId, timePeriod: "1m" as const, includeDocker: true }, + { enabled: enabled && systemId !== "", refetchInterval: 5_000 }, ); - const { data, error } = clientApi.widget.beszel.getSystemStats.useQuery(queryInput, { - enabled: enabled && systemId !== "", - }); - return { data: data ? { systemStats: data.systemStats, containerStats: data.containerStats } : null, error: error ?? (data?.error ? new Error(data.error) : null), diff --git a/packages/widgets/src/smart-home/entity-state/component.tsx b/packages/widgets/src/smart-home/entity-state/component.tsx index e729b8986..f1d6b6e18 100644 --- a/packages/widgets/src/smart-home/entity-state/component.tsx +++ b/packages/widgets/src/smart-home/entity-state/component.tsx @@ -25,7 +25,10 @@ export default function SmartHomeEntityStateWidget({ }; const { data: entityState } = clientApi.widget.smartHome.entityState.useQuery(input); - const { mutate } = clientApi.widget.smartHome.switchEntity.useMutation(); + const utils = clientApi.useUtils(); + const { mutate } = clientApi.widget.smartHome.switchEntity.useMutation({ + onSettled: () => void utils.widget.smartHome.entityState.invalidate(input), + }); const attribute = options.entityUnit.length > 0 ? " " + options.entityUnit : ""; diff --git a/packages/widgets/src/system-resources/component.tsx b/packages/widgets/src/system-resources/component.tsx index 3a1a0d378..352db4cd3 100644 --- a/packages/widgets/src/system-resources/component.tsx +++ b/packages/widgets/src/system-resources/component.tsx @@ -41,7 +41,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo const [items, setItems] = useState< { cpu: number; memory: number; gpu: number; network: { up: number; down: number } | null }[] - >([]); + >(() => (data[0] ? [toChartItem(data[0].healthInfo)] : [])); useEffect(() => { const firstItem = data[0];