diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index 7d06a8951..53b84c8db 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -2,6 +2,7 @@ import type { PropsWithChildren } from "react"; import { useState } from "react"; +import type { QueryKey } from "@tanstack/react-query"; import { QueryClient } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental"; @@ -33,6 +34,7 @@ import { import { createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/shared"; import { env } from "@homarr/common/env"; import { showWarningNotification } from "@homarr/notifications"; +import { widgetImports } from "@homarr/widgets"; import { createWidgetQueryPersister } from "./query-cache-persister"; @@ -90,6 +92,12 @@ export function TRPCReactProvider(props: PropsWithChildren) { }, }); client.setQueryDefaults([["widget"]], { refetchInterval: queryCacheDefaultRefetchIntervalMs }); + for (const { definition } of Object.values(widgetImports)) { + const def = definition as { refetchIntervalSeconds?: number; queryKey?: QueryKey; kind: string }; + if (!def.refetchIntervalSeconds) continue; + const key = def.queryKey ?? [["widget", def.kind]]; + client.setQueryDefaults(key, { refetchInterval: def.refetchIntervalSeconds * 1000 }); + } return client; }); diff --git a/packages/widgets/src/anchor-note/index.ts b/packages/widgets/src/anchor-note/index.ts index a54aada3f..a5e089c22 100644 --- a/packages/widgets/src/anchor-note/index.ts +++ b/packages/widgets/src/anchor-note/index.ts @@ -5,6 +5,8 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("anchorNote", { icon: IconNotes, + queryKey: [["widget", "anchorNotes"]], + refetchIntervalSeconds: 15, supportedIntegrations: ["anchor"], createOptions() { return optionsBuilder.from((factory) => ({ diff --git a/packages/widgets/src/archive-team-warrior/index.ts b/packages/widgets/src/archive-team-warrior/index.ts index 5e0998ec2..5917961f5 100644 --- a/packages/widgets/src/archive-team-warrior/index.ts +++ b/packages/widgets/src/archive-team-warrior/index.ts @@ -5,6 +5,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("archiveTeamWarrior", { icon: IconArchive, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ showBroadcastMessage: factory.switch({ diff --git a/packages/widgets/src/audio-stats/index.ts b/packages/widgets/src/audio-stats/index.ts index 25af0bdaf..0448ee016 100644 --- a/packages/widgets/src/audio-stats/index.ts +++ b/packages/widgets/src/audio-stats/index.ts @@ -14,6 +14,7 @@ const hideUnlessAudiobookshelf = { export const { definition, componentLoader } = createWidgetDefinition("audioStats", { icon: IconHeadphones, + refetchIntervalSeconds: 600, supportedIntegrations: ["navidrome", "audiobookshelf"], integrationsRequired: true, maxIntegrations: 1, diff --git a/packages/widgets/src/calendar/index.ts b/packages/widgets/src/calendar/index.ts index d03b2f610..40b246ab2 100644 --- a/packages/widgets/src/calendar/index.ts +++ b/packages/widgets/src/calendar/index.ts @@ -9,6 +9,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("calendar", { icon: IconCalendar, + refetchIntervalSeconds: 60, createOptions() { return optionsBuilder.from((factory) => ({ releaseType: factory.multiSelect({ diff --git a/packages/widgets/src/coolify/index.ts b/packages/widgets/src/coolify/index.ts index cebf9de05..e9c19ffe8 100644 --- a/packages/widgets/src/coolify/index.ts +++ b/packages/widgets/src/coolify/index.ts @@ -5,6 +5,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("coolify", { icon: IconCloud, + refetchIntervalSeconds: 30, createOptions() { return optionsBuilder.from((factory) => ({ showServers: factory.switch({ diff --git a/packages/widgets/src/definition.ts b/packages/widgets/src/definition.ts index aaf60269f..da8a83f23 100644 --- a/packages/widgets/src/definition.ts +++ b/packages/widgets/src/definition.ts @@ -70,6 +70,7 @@ export const createWidgetDefinition = ({ showToggleAllButtons: factory.switch({ diff --git a/packages/widgets/src/dns-hole/summary/index.ts b/packages/widgets/src/dns-hole/summary/index.ts index fe1c8b8a1..7c88d1b4a 100644 --- a/packages/widgets/src/dns-hole/summary/index.ts +++ b/packages/widgets/src/dns-hole/summary/index.ts @@ -9,6 +9,8 @@ export const widgetKind = "dnsHoleSummary"; export const { definition, componentLoader } = createWidgetDefinition(widgetKind, { icon: IconAd, + queryKey: [["widget", "dnsHole"]], + refetchIntervalSeconds: 5, createOptions() { return optionsBuilder.from((factory) => ({ usePiHoleColors: factory.switch({ diff --git a/packages/widgets/src/docker/component.tsx b/packages/widgets/src/docker/component.tsx index bbd961c1d..1c9cfe7bd 100644 --- a/packages/widgets/src/docker/component.tsx +++ b/packages/widgets/src/docker/component.tsx @@ -188,9 +188,7 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo data, refetch, isFetching, - } = clientApi.docker.getContainers.useQuery(undefined, { - refetchInterval: 30_000, - }); + } = clientApi.docker.getContainers.useQuery(undefined, {}); const containers = data?.containers ?? []; const timestamp = useMemo(() => data?.timestamp ?? new Date(), [data?.timestamp]); const relativeTime = useTimeAgo(timestamp); diff --git a/packages/widgets/src/docker/index.ts b/packages/widgets/src/docker/index.ts index 4a988c779..2dab9ec4a 100644 --- a/packages/widgets/src/docker/index.ts +++ b/packages/widgets/src/docker/index.ts @@ -26,6 +26,7 @@ const columnTranslationKeyMap = { export const { definition, componentLoader } = createWidgetDefinition("dockerContainers", { icon: IconBrandDocker, queryKey: [["docker", "getContainers"]], + refetchIntervalSeconds: 20, createOptions() { return optionsBuilder.from((factory) => ({ columns: factory.multiSelect({ diff --git a/packages/widgets/src/downloads/index.ts b/packages/widgets/src/downloads/index.ts index 36979083b..f125ee886 100644 --- a/packages/widgets/src/downloads/index.ts +++ b/packages/widgets/src/downloads/index.ts @@ -33,6 +33,7 @@ const columnsSort = columnsList.filter((column) => export const { definition, componentLoader } = createWidgetDefinition("downloads", { icon: IconDownload, + refetchIntervalSeconds: 5, createOptions() { return optionsBuilder.from( (factory) => ({ diff --git a/packages/widgets/src/firewall/index.ts b/packages/widgets/src/firewall/index.ts index 2278411de..07a2ab5ba 100644 --- a/packages/widgets/src/firewall/index.ts +++ b/packages/widgets/src/firewall/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("firewall", { icon: IconWall, + refetchIntervalSeconds: 5, createOptions() { return optionsBuilder.from(() => ({})); }, diff --git a/packages/widgets/src/health-monitoring/index.ts b/packages/widgets/src/health-monitoring/index.ts index fe31514de..7e47639de 100644 --- a/packages/widgets/src/health-monitoring/index.ts +++ b/packages/widgets/src/health-monitoring/index.ts @@ -8,6 +8,7 @@ import { createStorageVolumeMultiSelectOptions } from "../storage-volume-options export const { definition, componentLoader } = createWidgetDefinition("healthMonitoring", { icon: IconHeartRateMonitor, + refetchIntervalSeconds: 5, createOptions() { return optionsBuilder.from( (factory) => ({ diff --git a/packages/widgets/src/immich/album-carousel/index.ts b/packages/widgets/src/immich/album-carousel/index.ts index 9f8110ffc..565681d66 100644 --- a/packages/widgets/src/immich/album-carousel/index.ts +++ b/packages/widgets/src/immich/album-carousel/index.ts @@ -8,6 +8,8 @@ import { optionsBuilder } from "../../options"; export const { definition, componentLoader } = createWidgetDefinition("immich-albumCarousel", { icon: IconPhoto, + queryKey: [["widget", "immich"]], + refetchIntervalSeconds: 900, supportedIntegrations: ["immich"], integrationsRequired: true, createOptions() { diff --git a/packages/widgets/src/immich/server-stats/index.ts b/packages/widgets/src/immich/server-stats/index.ts index 61b07289a..026e2a792 100644 --- a/packages/widgets/src/immich/server-stats/index.ts +++ b/packages/widgets/src/immich/server-stats/index.ts @@ -5,6 +5,8 @@ import { optionsBuilder } from "../../options"; export const { definition, componentLoader } = createWidgetDefinition("immich-serverStats", { icon: IconGraphFilled, + queryKey: [["widget", "immich"]], + refetchIntervalSeconds: 900, supportedIntegrations: ["immich"], integrationsRequired: true, createOptions() { diff --git a/packages/widgets/src/indexer-manager/index.ts b/packages/widgets/src/indexer-manager/index.ts index c2160999f..89b825146 100644 --- a/packages/widgets/src/indexer-manager/index.ts +++ b/packages/widgets/src/indexer-manager/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("indexerManager", { icon: IconReportSearch, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ openIndexerSiteInNewTab: factory.switch({ diff --git a/packages/widgets/src/media-releases/index.ts b/packages/widgets/src/media-releases/index.ts index af650eef7..a4cf185f5 100644 --- a/packages/widgets/src/media-releases/index.ts +++ b/packages/widgets/src/media-releases/index.ts @@ -5,6 +5,8 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("mediaReleases", { icon: IconTicket, + queryKey: [["widget", "mediaRelease"]], + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ layout: factory.select({ diff --git a/packages/widgets/src/media-requests/list/index.ts b/packages/widgets/src/media-requests/list/index.ts index d4e0fcd54..aaa436eb7 100644 --- a/packages/widgets/src/media-requests/list/index.ts +++ b/packages/widgets/src/media-requests/list/index.ts @@ -8,6 +8,8 @@ import { optionsBuilder } from "../../options"; export const { componentLoader, definition } = createWidgetDefinition("mediaRequests-requestList", { icon: IconZoomQuestion, + queryKey: [["widget", "mediaRequests"]], + refetchIntervalSeconds: 60, createOptions() { return optionsBuilder.from((factory) => ({ linksTargetNewTab: factory.switch({ diff --git a/packages/widgets/src/media-requests/stats/index.ts b/packages/widgets/src/media-requests/stats/index.ts index d49cb8495..5ca4993ba 100644 --- a/packages/widgets/src/media-requests/stats/index.ts +++ b/packages/widgets/src/media-requests/stats/index.ts @@ -7,6 +7,8 @@ import { createWidgetDefinition } from "../../definition"; export const { componentLoader, definition } = createWidgetDefinition("mediaRequests-requestStats", { icon: IconChartBar, + queryKey: [["widget", "mediaRequests"]], + refetchIntervalSeconds: 60, createOptions() { return {}; }, diff --git a/packages/widgets/src/media-server/index.ts b/packages/widgets/src/media-server/index.ts index b85d09857..544fc4933 100644 --- a/packages/widgets/src/media-server/index.ts +++ b/packages/widgets/src/media-server/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../options"; export const { componentLoader, definition } = createWidgetDefinition("mediaServer", { icon: IconVideo, + refetchIntervalSeconds: 5, createOptions() { return optionsBuilder.from((factory) => ({ showOnlyPlaying: factory.switch({ defaultValue: true, withDescription: true }), diff --git a/packages/widgets/src/media-transcoding/index.ts b/packages/widgets/src/media-transcoding/index.ts index 2753925d8..152eddd93 100644 --- a/packages/widgets/src/media-transcoding/index.ts +++ b/packages/widgets/src/media-transcoding/index.ts @@ -11,6 +11,7 @@ export const views = ["workers", "queue", "statistics"] as const; export const { componentLoader, definition } = createWidgetDefinition("mediaTranscoding", { icon: IconTransform, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ defaultView: factory.select({ diff --git a/packages/widgets/src/minecraft/server-status/index.ts b/packages/widgets/src/minecraft/server-status/index.ts index c5cd0e068..b89beeed3 100644 --- a/packages/widgets/src/minecraft/server-status/index.ts +++ b/packages/widgets/src/minecraft/server-status/index.ts @@ -6,6 +6,7 @@ import { optionsBuilder } from "../../options"; export const { componentLoader, definition } = createWidgetDefinition("minecraftServerStatus", { icon: IconBrandMinecraft, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ title: factory.text({ defaultValue: "" }), diff --git a/packages/widgets/src/network-controller/network-status/index.ts b/packages/widgets/src/network-controller/network-status/index.ts index 4af2fbaf0..bf9c026a3 100644 --- a/packages/widgets/src/network-controller/network-status/index.ts +++ b/packages/widgets/src/network-controller/network-status/index.ts @@ -7,6 +7,8 @@ import { optionsBuilder } from "../../options"; export const { definition, componentLoader } = createWidgetDefinition("networkControllerStatus", { icon: IconTopologyFull, + queryKey: [["widget", "networkController"]], + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ content: factory.select({ diff --git a/packages/widgets/src/network-controller/summary/index.ts b/packages/widgets/src/network-controller/summary/index.ts index dd541024a..d60629f42 100644 --- a/packages/widgets/src/network-controller/summary/index.ts +++ b/packages/widgets/src/network-controller/summary/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../../options"; export const { definition, componentLoader } = createWidgetDefinition("networkControllerSummary", { icon: IconTopologyFull, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from(() => ({})); }, diff --git a/packages/widgets/src/notifications/index.ts b/packages/widgets/src/notifications/index.ts index 553ef5d9f..ae5fb3432 100644 --- a/packages/widgets/src/notifications/index.ts +++ b/packages/widgets/src/notifications/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../options"; export const { componentLoader, definition } = createWidgetDefinition("notifications", { icon: IconMessage, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ hideLogos: factory.switch({ defaultValue: false }), diff --git a/packages/widgets/src/paperless-ngx/index.ts b/packages/widgets/src/paperless-ngx/index.ts index 59af7ca59..db6b35041 100644 --- a/packages/widgets/src/paperless-ngx/index.ts +++ b/packages/widgets/src/paperless-ngx/index.ts @@ -5,6 +5,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("paperlessNgx", { icon: IconFileText, + refetchIntervalSeconds: 900, supportedIntegrations: ["paperlessNgx"], integrationsRequired: true, createOptions() { diff --git a/packages/widgets/src/releases/index.ts b/packages/widgets/src/releases/index.ts index 2c3d1d3a2..48cdf77d6 100644 --- a/packages/widgets/src/releases/index.ts +++ b/packages/widgets/src/releases/index.ts @@ -14,6 +14,7 @@ const relativeDateSchema = z export const { definition, componentLoader } = createWidgetDefinition("releases", { icon: IconRocket, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ newReleaseWithin: factory.text({ diff --git a/packages/widgets/src/rssFeed/index.ts b/packages/widgets/src/rssFeed/index.ts index 079c761a9..4fc0b523c 100644 --- a/packages/widgets/src/rssFeed/index.ts +++ b/packages/widgets/src/rssFeed/index.ts @@ -12,6 +12,7 @@ import { optionsBuilder } from "../options"; */ export const { definition, componentLoader } = createWidgetDefinition("rssFeed", { icon: IconRss, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ feedUrls: factory.multiText({ diff --git a/packages/widgets/src/smart-home/entity-state/index.ts b/packages/widgets/src/smart-home/entity-state/index.ts index a4dd32921..2244499b5 100644 --- a/packages/widgets/src/smart-home/entity-state/index.ts +++ b/packages/widgets/src/smart-home/entity-state/index.ts @@ -7,6 +7,8 @@ import { optionsBuilder } from "../../options"; export const { definition, componentLoader } = createWidgetDefinition("smartHome-entityState", { icon: IconBinaryTree, + queryKey: [["widget", "smartHome"]], + refetchIntervalSeconds: 60, createOptions() { return optionsBuilder.from((factory) => ({ entityId: factory.text({ diff --git a/packages/widgets/src/speedtest-tracker/index.ts b/packages/widgets/src/speedtest-tracker/index.ts index ea3c0c5d2..97ce43e49 100644 --- a/packages/widgets/src/speedtest-tracker/index.ts +++ b/packages/widgets/src/speedtest-tracker/index.ts @@ -5,6 +5,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("speedtestTracker", { icon: IconSpeedboat, + refetchIntervalSeconds: 600, createOptions() { return optionsBuilder.from( (factory) => ({ diff --git a/packages/widgets/src/stocks/index.ts b/packages/widgets/src/stocks/index.ts index fa64bf3a4..e230a2236 100644 --- a/packages/widgets/src/stocks/index.ts +++ b/packages/widgets/src/stocks/index.ts @@ -13,6 +13,7 @@ const timeIntervalOptions = stockPriceTimeFrames.interval; export const { definition, componentLoader } = createWidgetDefinition("stockPrice", { icon: IconBuildingBank, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from((factory) => ({ stock: factory.text({ diff --git a/packages/widgets/src/system-disks/index.ts b/packages/widgets/src/system-disks/index.ts index 61e4a5c05..bd8cbd3a1 100644 --- a/packages/widgets/src/system-disks/index.ts +++ b/packages/widgets/src/system-disks/index.ts @@ -6,6 +6,8 @@ import { createStorageVolumeMultiSelectOptions } from "../storage-volume-options export const { definition, componentLoader } = createWidgetDefinition("systemDisks", { icon: IconServer2, + queryKey: [["widget", "healthMonitoring"]], + refetchIntervalSeconds: 5, supportedIntegrations: ["dashDot", "openmediavault", "truenas", "unraid", "synology"], createOptions() { return optionsBuilder.from( diff --git a/packages/widgets/src/system-resources/index.ts b/packages/widgets/src/system-resources/index.ts index 8d4837187..06084bc5f 100644 --- a/packages/widgets/src/system-resources/index.ts +++ b/packages/widgets/src/system-resources/index.ts @@ -14,6 +14,8 @@ const labelDisplayModeOptions = { export const { definition, componentLoader } = createWidgetDefinition("systemResources", { icon: IconGraphFilled, + queryKey: [["widget", "healthMonitoring"]], + refetchIntervalSeconds: 5, supportedIntegrations: ["dashDot", "openmediavault", "truenas", "unraid", "glances", "synology"], createOptions() { return optionsBuilder.from((factory) => ({ diff --git a/packages/widgets/src/timetable/index.ts b/packages/widgets/src/timetable/index.ts index cee44323c..6e1c7b53f 100644 --- a/packages/widgets/src/timetable/index.ts +++ b/packages/widgets/src/timetable/index.ts @@ -8,6 +8,7 @@ import { optionsBuilder } from "../options"; export const { componentLoader, definition } = createWidgetDefinition("timetable", { icon: IconBusStop, + refetchIntervalSeconds: 60, createOptions() { return optionsBuilder.from((factory) => ({ baseUrl: factory.text({ diff --git a/packages/widgets/src/tracearr/index.ts b/packages/widgets/src/tracearr/index.ts index 44936cb67..db455243b 100644 --- a/packages/widgets/src/tracearr/index.ts +++ b/packages/widgets/src/tracearr/index.ts @@ -5,6 +5,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("tracearr", { icon: IconActivityHeartbeat, + refetchIntervalSeconds: 5, createOptions() { return optionsBuilder.from((factory) => ({ showStreams: factory.switch({ diff --git a/packages/widgets/src/umami/index.ts b/packages/widgets/src/umami/index.ts index c55ecd261..1d3371b76 100644 --- a/packages/widgets/src/umami/index.ts +++ b/packages/widgets/src/umami/index.ts @@ -8,6 +8,7 @@ export const timeFrameValues = ["today", "24h", "7d", "30d", "month", "lastMonth export const { definition, componentLoader } = createWidgetDefinition("umami", { icon: IconChartBar, + refetchIntervalSeconds: 300, supportedIntegrations: ["umami"], createOptions() { return optionsBuilder.from( diff --git a/packages/widgets/src/ups/index.ts b/packages/widgets/src/ups/index.ts index 681daf417..f040cc30a 100644 --- a/packages/widgets/src/ups/index.ts +++ b/packages/widgets/src/ups/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("ups", { icon: IconBatteryCharging, + refetchIntervalSeconds: 60, createOptions() { return optionsBuilder.from((factory) => ({ showBattery: factory.switch({ diff --git a/packages/widgets/src/uptime-kuma/index.ts b/packages/widgets/src/uptime-kuma/index.ts index b9eebcd8c..b381c9390 100644 --- a/packages/widgets/src/uptime-kuma/index.ts +++ b/packages/widgets/src/uptime-kuma/index.ts @@ -5,6 +5,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("uptimeKuma", { icon: IconHeartbeat, + refetchIntervalSeconds: 60, createOptions() { return optionsBuilder.from((factory) => ({ showAverageUptime: factory.switch({ diff --git a/packages/widgets/src/vpn/index.ts b/packages/widgets/src/vpn/index.ts index 426366a66..3e42724dd 100644 --- a/packages/widgets/src/vpn/index.ts +++ b/packages/widgets/src/vpn/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../options"; export const { componentLoader, definition } = createWidgetDefinition("vpn", { icon: IconShieldLock, + refetchIntervalSeconds: 300, createOptions() { return optionsBuilder.from(() => ({})); }, diff --git a/packages/widgets/src/weather/index.ts b/packages/widgets/src/weather/index.ts index 6c30b6df8..9133b8b42 100644 --- a/packages/widgets/src/weather/index.ts +++ b/packages/widgets/src/weather/index.ts @@ -7,6 +7,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("weather", { icon: IconCloud, + refetchIntervalSeconds: 60, createOptions() { return optionsBuilder.from( (factory) => ({