fix: address PR review — bugs, dead code, error handling

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
This commit is contained in:
Thomas Camlong committed 2026-06-25 11:25:09 +02:00
1 parent 457b1b0ce0
commit c4a664b7ac
50 files changed
+31 -254

No files matched your search

@@ -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<AnchorNote, "anchor", { noteId: string }>({
queryKey: "anchorNote",
async requestAsync(integration, input) {
const instance = await createIntegrationAsync(integration);
return instance.getNoteAsync(input.noteId);
@@ -13,5 +13,4 @@ export const archiveTeamWarriorRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getStatusAsync();
},
queryKey: "archiveTeamWarriorStatus",
});
@@ -12,5 +12,4 @@ export const audiobookshelfRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
queryKey: "audiobookshelfDashboard",
});
+8 -5
View File
@@ -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<string, { type: string; perPage: number }> = {
@@ -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",
});
-1
View File
@@ -23,5 +23,4 @@ export const calendarMonthRequestHandler = createIntegrationRequestHandler<
input.showUnmonitored,
);
},
queryKey: "calendarMonth",
});
-1
View File
@@ -12,5 +12,4 @@ export const coolifyRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getInstanceInfoAsync();
},
queryKey: "coolifyInfo",
});
-1
View File
@@ -13,5 +13,4 @@ export const dnsHoleRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getSummaryAsync();
},
queryKey: "dnsHoleSummary",
});
-1
View File
@@ -171,7 +171,6 @@ const mockContainers: {
];
export const dockerContainersRequestHandler = createWidgetRequestHandler({
queryKey: "dockerContainersResult",
async requestAsync() {
if (isDemoMode) {
return mockContainers;
@@ -13,5 +13,4 @@ export const downloadClientRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getClientJobsAndStatusAsync(input);
},
queryKey: "downloadClientJobStatus",
});
@@ -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 [];
-4
View File
@@ -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",
});
@@ -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",
});
-3
View File
@@ -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",
});
@@ -13,5 +13,4 @@ export const indexerManagerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getIndexersAsync();
},
queryKey: "indexerManager",
});
@@ -11,7 +11,6 @@ type IntegrationOfKind<TKind extends IntegrationKind> = Omit<Integration, "kind"
};
interface Options<TData, TKind extends IntegrationKind, TInput extends Record<string, unknown>> {
queryKey: string;
requestAsync: (integration: IntegrationOfKind<TKind>, input: TInput) => Promise<TData>;
}
@@ -24,7 +23,6 @@ export const createIntegrationRequestHandler = <
) => ({
handler: (integration: IntegrationOfKind<TKind>, itemOptions: TInput) =>
createRequestHandler<TData, { options: TInput; integration: IntegrationOfKind<TKind> }>({
queryKey: options.queryKey,
requestAsync: async (input) => options.requestAsync(input.integration, input.options),
}).handler({ options: itemOptions, integration }),
});
@@ -1,5 +1,4 @@
interface Options<TData, TInput extends Record<string, unknown>> {
queryKey: string;
requestAsync: (input: TInput) => Promise<TData>;
}
@@ -1,7 +1,6 @@
import { createRequestHandler } from "./request-handler";
interface Options<TData, TInput extends Record<string, unknown>> {
queryKey: string;
requestAsync: (input: TInput) => Promise<TData>;
}
@@ -10,7 +9,6 @@ export const createWidgetRequestHandler = <TData, TInput extends Record<string,
) => ({
handler: (widgetOptions: TInput) =>
createRequestHandler<TData, TInput>({
queryKey: options.queryKey,
requestAsync: options.requestAsync,
}).handler(widgetOptions),
});
@@ -13,5 +13,4 @@ export const mediaReleaseRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getMediaReleasesAsync();
},
queryKey: "mediaReleases",
});
@@ -13,5 +13,4 @@ export const mediaRequestListRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getRequestsAsync();
},
queryKey: "mediaRequestList",
});
@@ -16,5 +16,4 @@ export const mediaRequestStatsRequestHandler = createIntegrationRequestHandler<
users: await integrationInstance.getUsersAsync(),
};
},
queryKey: "mediaRequestStats",
});
@@ -15,5 +15,4 @@ export const mediaServerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getCurrentSessionsAsync({ showOnlyPlaying: input.showOnlyPlaying });
},
queryKey: "mediaServerSessions",
});
@@ -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 {
@@ -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}`;
@@ -12,5 +12,4 @@ export const navidromeRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
queryKey: "navidromeDashboard",
});
@@ -13,5 +13,4 @@ export const networkControllerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getNetworkSummaryAsync();
},
queryKey: "networkControllerSummary",
});
@@ -13,5 +13,4 @@ export const notificationsRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getNotificationsAsync();
},
queryKey: "notificationsJobStatus",
});
@@ -12,5 +12,4 @@ export const paperlessNgxStatsRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getStatsAsync();
},
queryKey: "paperless-ngx-stats",
});
-1
View File
@@ -13,5 +13,4 @@ export const releasesRequestHandler = createIntegrationRequestHandler<
const instance = await createIntegrationAsync(integration);
return instance.getLatestMatchingReleaseAsync(input.identifier, input.versionRegex);
},
queryKey: "repositoriesReleases",
});
@@ -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) => {
@@ -18,5 +18,4 @@ export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandle
return result.data.state;
},
queryKey: "smartHome-entityState",
});
@@ -12,5 +12,4 @@ export const speedtestTrackerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
queryKey: "speedtestTrackerDashboard",
});
@@ -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(
@@ -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",
});
-1
View File
@@ -12,5 +12,4 @@ export const tracearrRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
queryKey: "tracearrDashboard",
});
-6
View File
@@ -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<string[], "umami", { websiteId: string }>({
@@ -20,7 +19,6 @@ export const umamiEventNamesRequestHandler = createIntegrationRequestHandler<str
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getEventNamesAsync(input.websiteId);
},
queryKey: "umamiEventNames",
});
export const umamiTopPagesRequestHandler = createIntegrationRequestHandler<
@@ -32,7 +30,6 @@ export const umamiTopPagesRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getTopPagesAsync(input.websiteId, input.timeFrame, input.limit);
},
queryKey: "umamiTopPages",
});
export const umamiTopReferrersRequestHandler = createIntegrationRequestHandler<
@@ -44,7 +41,6 @@ export const umamiTopReferrersRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getTopReferrersAsync(input.websiteId, input.timeFrame, input.limit);
},
queryKey: "umamiTopReferrers",
});
export const umamiMultiEventRequestHandler = createIntegrationRequestHandler<
@@ -56,7 +52,6 @@ export const umamiMultiEventRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getMultiEventTimeSeriesAsync(input.websiteId, input.timeFrame, input.eventNames);
},
queryKey: "umamiMultiEvent",
});
export const umamiActiveVisitorsRequestHandler = createIntegrationRequestHandler<
@@ -68,5 +63,4 @@ export const umamiActiveVisitorsRequestHandler = createIntegrationRequestHandler
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getActiveVisitorsAsync(input.websiteId);
},
queryKey: "umamiActiveVisitors",
});
@@ -12,7 +12,6 @@ import packageJson from "../../../package.json";
const logger = createLogger({ module: "updateCheckerRequestHandler" });
export const updateCheckerRequestHandler = createRequestHandler({
queryKey: "homarr-update-checker",
async requestAsync(_) {
return {
availableUpdates: await getAvailableUpdatesAsync(packageJson.version),
-1
View File
@@ -13,5 +13,4 @@ export const upsSummariesRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getUpsSummariesAsync();
},
queryKey: "upsSummaries",
});
@@ -12,5 +12,4 @@ export const uptimeKumaRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
queryKey: "uptimeKumaDashboard",
});
-1
View File
@@ -6,7 +6,6 @@ import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { createWidgetRequestHandler } from "./lib/widget-request-handler";
export const weatherRequestHandler = createWidgetRequestHandler({
queryKey: "weatherAtLocation",
async requestAsync(input: { latitude: number; longitude: number }) {
const res = await withTimeoutAsync(async (signal) => {
return await fetchWithTrustedCertificatesAsync(