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:
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",
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
@@ -23,5 +23,4 @@ export const calendarMonthRequestHandler = createIntegrationRequestHandler<
|
||||
input.showUnmonitored,
|
||||
);
|
||||
},
|
||||
queryKey: "calendarMonth",
|
||||
});
|
||||
@@ -12,5 +12,4 @@ export const coolifyRequestHandler = createIntegrationRequestHandler<
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
return await integrationInstance.getInstanceInfoAsync();
|
||||
},
|
||||
queryKey: "coolifyInfo",
|
||||
});
|
||||
@@ -13,5 +13,4 @@ export const dnsHoleRequestHandler = createIntegrationRequestHandler<
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
return await integrationInstance.getSummaryAsync();
|
||||
},
|
||||
queryKey: "dnsHoleSummary",
|
||||
});
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
@@ -12,5 +12,4 @@ export const tracearrRequestHandler = createIntegrationRequestHandler<
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
return await integrationInstance.getDashboardDataAsync();
|
||||
},
|
||||
queryKey: "tracearrDashboard",
|
||||
});
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user