Compare commits

..
Author SHA1 Message Date
ajnart e18a040686 style: auto-fix lint and format 2026-07-15 09:31:02 +00:00
ajnart 789e71096e fix(widgets): add per-widget refetchIntervalSeconds to WidgetDefinition
The v1.69.0 caching refactor replaced Redis pub/sub push with a global
30s client-side refetchInterval. This caused fast-refresh widgets like
health-monitoring (Dashdot, 5s) to only update every 30s.

Adds refetchIntervalSeconds field to WidgetDefinition. Each widget
declares its own refresh rate alongside its other config. The TRPC
provider iterates all widget definitions and calls setQueryDefaults
with the widget's queryKey (or auto-derived from widget kind).

Widgets whose kind doesn't match their tRPC path set queryKey
explicitly (e.g. dnsHoleSummary -> widget.dnsHole, systemResources ->
widget.healthMonitoring). Beszel already does this for cache sharing.

Removed inline refetchInterval from docker widget component since it's
now on the definition.

Fixes #6268
2026-07-15 11:29:24 +02:00
74 changed files with 61 additions and 56 deletions

No files matched your search

@@ -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;
});
@@ -12,7 +12,6 @@ export const anchorNotesListRequestHandler = createIntegrationRequestHandler<
const instance = await createIntegrationAsync(integration);
return instance.listNotesAsync(input);
},
cacheTtlMs: 15_000,
});
export const anchorNoteRequestHandler = createIntegrationRequestHandler<AnchorNote, "anchor", { noteId: string }>({
@@ -20,5 +19,4 @@ export const anchorNoteRequestHandler = createIntegrationRequestHandler<AnchorNo
const instance = await createIntegrationAsync(integration);
return instance.getNoteAsync(input.noteId);
},
cacheTtlMs: 15_000,
});
@@ -13,5 +13,4 @@ export const archiveTeamWarriorRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getStatusAsync();
},
cacheTtlMs: 300_000,
});
@@ -12,5 +12,4 @@ export const audiobookshelfRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
cacheTtlMs: 600_000,
});
-1
View File
@@ -23,5 +23,4 @@ export const calendarMonthRequestHandler = createIntegrationRequestHandler<
input.showUnmonitored,
);
},
cacheTtlMs: 60_000,
});
-1
View File
@@ -12,5 +12,4 @@ export const coolifyRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getInstanceInfoAsync();
},
cacheTtlMs: 30_000,
});
-1
View File
@@ -13,5 +13,4 @@ export const dnsHoleRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getSummaryAsync();
},
cacheTtlMs: 5_000,
});
-1
View File
@@ -177,7 +177,6 @@ export const dockerContainersRequestHandler = createWidgetRequestHandler({
}
return await getContainersWithStatsAsync();
},
cacheTtlMs: 20_000,
});
const extractImage = (container: ContainerInfo) => extractContainerImageName(container.Image);
@@ -13,5 +13,4 @@ export const downloadClientRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getClientJobsAndStatusAsync(input);
},
cacheTtlMs: 5_000,
});
@@ -69,5 +69,4 @@ export const duckDuckGoBangsRequestHandler = createRequestHandler({
return normalized;
},
cacheTtlMs: 86_400_000,
});
-4
View File
@@ -18,7 +18,6 @@ export const firewallCpuRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return integrationInstance.getFirewallCpuAsync();
},
cacheTtlMs: 5_000,
});
export const firewallMemoryRequestHandler = createIntegrationRequestHandler<
@@ -30,7 +29,6 @@ export const firewallMemoryRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getFirewallMemoryAsync();
},
cacheTtlMs: 15_000,
});
export const firewallInterfacesRequestHandler = createIntegrationRequestHandler<
@@ -42,7 +40,6 @@ export const firewallInterfacesRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getFirewallInterfacesAsync();
},
cacheTtlMs: 30_000,
});
export const firewallVersionRequestHandler = createIntegrationRequestHandler<
@@ -54,5 +51,4 @@ export const firewallVersionRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getFirewallVersionAsync();
},
cacheTtlMs: 3_600_000,
});
@@ -13,7 +13,6 @@ export const systemInfoRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getSystemInfoAsync();
},
cacheTtlMs: 5_000,
});
export const clusterInfoRequestHandler = createIntegrationRequestHandler<
@@ -25,5 +24,4 @@ export const clusterInfoRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getClusterInfoAsync();
},
cacheTtlMs: 5_000,
});
-3
View File
@@ -13,7 +13,6 @@ export const immichStatsRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getServerStatsAsync();
},
cacheTtlMs: 900_000,
});
export const immichAlbumsRequestHandler = createIntegrationRequestHandler<
@@ -29,7 +28,6 @@ export const immichAlbumsRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getAlbumsAsync();
},
cacheTtlMs: 900_000,
});
export const immichAlbumRequestHandler = createIntegrationRequestHandler<
@@ -41,5 +39,4 @@ export const immichAlbumRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getAlbumAsync(input.albumId);
},
cacheTtlMs: 900_000,
});
@@ -13,5 +13,4 @@ export const indexerManagerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getIndexersAsync();
},
cacheTtlMs: 300_000,
});
@@ -2,7 +2,6 @@ import { createRequestHandler } from "./request-handler";
interface Options<TData, TInput extends Record<string, unknown>> {
requestAsync: (input: TInput) => Promise<TData>;
cacheTtlMs?: number;
}
export const createWidgetRequestHandler = <TData, TInput extends Record<string, unknown>>(
@@ -11,6 +10,5 @@ export const createWidgetRequestHandler = <TData, TInput extends Record<string,
handler: (widgetOptions: TInput) =>
createRequestHandler<TData, TInput>({
requestAsync: options.requestAsync,
cacheTtlMs: options.cacheTtlMs,
}).handler(widgetOptions),
});
@@ -13,5 +13,4 @@ export const mediaReleaseRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getMediaReleasesAsync();
},
cacheTtlMs: 300_000,
});
@@ -16,5 +16,4 @@ export const mediaRequestStatsRequestHandler = createIntegrationRequestHandler<
users: await integrationInstance.getUsersAsync(),
};
},
cacheTtlMs: 60_000,
});
@@ -15,5 +15,4 @@ export const mediaServerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getCurrentSessionsAsync({ showOnlyPlaying: input.showOnlyPlaying });
},
cacheTtlMs: 5_000,
});
@@ -17,7 +17,6 @@ export const mediaTranscodingRequestHandler = createIntegrationRequestHandler<
statistics: await integrationInstance.getStatisticsAsync(),
};
},
cacheTtlMs: 300_000,
});
export interface MediaTranscoding {
@@ -14,7 +14,6 @@ export const minecraftServerStatusRequestHandler = createWidgetRequestHandler({
);
return responseSchema.parse(await response.json());
},
cacheTtlMs: 300_000,
});
const responseSchema = z
@@ -12,5 +12,4 @@ export const navidromeRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
cacheTtlMs: 600_000,
});
@@ -13,5 +13,4 @@ export const networkControllerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getNetworkSummaryAsync();
},
cacheTtlMs: 300_000,
});
@@ -13,5 +13,4 @@ export const notificationsRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getNotificationsAsync();
},
cacheTtlMs: 300_000,
});
@@ -12,5 +12,4 @@ export const paperlessNgxStatsRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getStatsAsync();
},
cacheTtlMs: 900_000,
});
@@ -28,7 +28,6 @@ export const rssFeedsRequestHandler = createWidgetRequestHandler({
entries: result.entries?.map((entry) => ({ ...entry, feedUrl: input.url })).slice(0, input.count) ?? [],
};
},
cacheTtlMs: 300_000,
});
const attemptGetImageFromEntry = (feedUrl: string, entry: object) => {
@@ -18,5 +18,4 @@ export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandle
return result.data.state;
},
cacheTtlMs: 60_000,
});
@@ -12,5 +12,4 @@ export const speedtestTrackerRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
cacheTtlMs: 600_000,
});
@@ -39,7 +39,6 @@ export const fetchStockPriceHandler = createWidgetRequestHandler({
shortName: firstResult.meta.shortName,
};
},
cacheTtlMs: 300_000,
});
const dataSchema = z
-1
View File
@@ -12,5 +12,4 @@ export const tracearrRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
cacheTtlMs: 5_000,
});
-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);
},
cacheTtlMs: 300_000,
});
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);
},
cacheTtlMs: 300_000,
});
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);
},
cacheTtlMs: 300_000,
});
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);
},
cacheTtlMs: 300_000,
});
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);
},
cacheTtlMs: 300_000,
});
export const umamiActiveVisitorsRequestHandler = createIntegrationRequestHandler<
@@ -68,5 +63,4 @@ export const umamiActiveVisitorsRequestHandler = createIntegrationRequestHandler
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getActiveVisitorsAsync(input.websiteId);
},
cacheTtlMs: 30_000,
});
-1
View File
@@ -13,5 +13,4 @@ export const upsSummariesRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getUpsSummariesAsync();
},
cacheTtlMs: 60_000,
});
@@ -12,5 +12,4 @@ export const uptimeKumaRequestHandler = createIntegrationRequestHandler<
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getDashboardDataAsync();
},
cacheTtlMs: 60_000,
});
-1
View File
@@ -25,5 +25,4 @@ export const vpnSummaryHandler = createIntegrationRequestHandler<
return null;
}
},
cacheTtlMs: 300_000,
});
-1
View File
@@ -31,7 +31,6 @@ export const weatherRequestHandler = createWidgetRequestHandler({
}),
} satisfies Weather;
},
cacheTtlMs: 60_000,
});
const atLocationOutput = z.object({
@@ -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) => ({
@@ -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({
@@ -14,6 +14,7 @@ const hideUnlessAudiobookshelf = {
export const { definition, componentLoader } = createWidgetDefinition("audioStats", {
icon: IconHeadphones,
refetchIntervalSeconds: 600,
supportedIntegrations: ["navidrome", "audiobookshelf"],
integrationsRequired: true,
maxIntegrations: 1,
+1
View File
@@ -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({
+1
View File
@@ -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({
+1
View File
@@ -70,6 +70,7 @@ export const createWidgetDefinition = <TKind extends WidgetKind, TDefinition ext
export interface WidgetDefinition {
icon: TablerIcon;
queryKey?: QueryKey;
refetchIntervalSeconds?: number;
supportedIntegrations?: IntegrationKind[];
integrationsRequired?: boolean;
maxIntegrations?: number;
@@ -9,6 +9,8 @@ export const widgetKind = "dnsHoleControls";
export const { definition, componentLoader } = createWidgetDefinition(widgetKind, {
icon: IconDeviceGamepad,
queryKey: [["widget", "dnsHole"]],
refetchIntervalSeconds: 5,
createOptions() {
return optionsBuilder.from((factory) => ({
showToggleAllButtons: factory.switch({
@@ -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({
+2 -10
View File
@@ -184,13 +184,7 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo
const t = useScopedI18n("docker");
const isTiny = width <= 256;
const {
data,
refetch,
isFetching,
} = clientApi.docker.getContainers.useQuery(undefined, {
refetchInterval: 30_000,
});
const { data, refetch, isFetching } = clientApi.docker.getContainers.useQuery(undefined, {});
const containers = data?.containers ?? [];
const timestamp = useMemo(() => data?.timestamp ?? new Date(), [data?.timestamp]);
const relativeTime = useTimeAgo(timestamp);
@@ -304,9 +298,7 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo
{t("table.totalMemory", { memory: formatBytes(totals.memory) })}
</Text>
<Tooltip
label={t("table.refresh.lastUpdated", { when: relativeTime })}
>
<Tooltip label={t("table.refresh.lastUpdated", { when: relativeTime })}>
<ActionIcon
size="sm"
variant="transparent"
+1
View File
@@ -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({
+1
View File
@@ -33,6 +33,7 @@ const columnsSort = columnsList.filter((column) =>
export const { definition, componentLoader } = createWidgetDefinition("downloads", {
icon: IconDownload,
refetchIntervalSeconds: 5,
createOptions() {
return optionsBuilder.from(
(factory) => ({
+1
View File
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("firewall", {
icon: IconWall,
refetchIntervalSeconds: 5,
createOptions() {
return optionsBuilder.from(() => ({}));
},
@@ -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) => ({
@@ -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() {
@@ -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() {
@@ -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({
@@ -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({
@@ -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({
@@ -7,6 +7,8 @@ import { createWidgetDefinition } from "../../definition";
export const { componentLoader, definition } = createWidgetDefinition("mediaRequests-requestStats", {
icon: IconChartBar,
queryKey: [["widget", "mediaRequests"]],
refetchIntervalSeconds: 60,
createOptions() {
return {};
},
@@ -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 }),
@@ -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({
@@ -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: "" }),
@@ -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({
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../../options";
export const { definition, componentLoader } = createWidgetDefinition("networkControllerSummary", {
icon: IconTopologyFull,
refetchIntervalSeconds: 300,
createOptions() {
return optionsBuilder.from(() => ({}));
},
@@ -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 }),
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("paperlessNgx", {
icon: IconFileText,
refetchIntervalSeconds: 900,
supportedIntegrations: ["paperlessNgx"],
integrationsRequired: true,
createOptions() {
+1
View File
@@ -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({
+1
View File
@@ -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({
@@ -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({
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("speedtestTracker", {
icon: IconSpeedboat,
refetchIntervalSeconds: 600,
createOptions() {
return optionsBuilder.from(
(factory) => ({
+1
View File
@@ -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({
@@ -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(
@@ -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) => ({
+1
View File
@@ -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({
+1
View File
@@ -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({
+1
View File
@@ -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(
+1
View File
@@ -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({
@@ -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({
+1
View File
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../options";
export const { componentLoader, definition } = createWidgetDefinition("vpn", {
icon: IconShieldLock,
refetchIntervalSeconds: 300,
createOptions() {
return optionsBuilder.from(() => ({}));
},
+1
View File
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("weather", {
icon: IconCloud,
refetchIntervalSeconds: 60,
createOptions() {
return optionsBuilder.from(
(factory) => ({