fix(demo): stop seeded widget background polling (#6980)
* fix(demo): stop seeded widget background polling * docs(demo): clarify single-replica requirements * fix: abort startup when auth secret generation fails * docs: remove demo deployment changes from PR * fix: scope demo polling changes
This commit is contained in:
1 parent
a17845aa1c
commit
4dbe4343e5
10 files changed
+48
-19
No files matched your search
@@ -34,6 +34,7 @@ import { createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/shared";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { showWarningNotification } from "@homarr/notifications";
|
||||
import { DemoReadOnlyProvider } from "@homarr/widgets/demo-read-only";
|
||||
import { widgetQueryRefetchIntervals } from "@homarr/widgets/refetch-intervals";
|
||||
|
||||
import { useAuthContext } from "./session";
|
||||
@@ -66,7 +67,7 @@ const constructWebsocketUrl = () => {
|
||||
return `${getWebSocketProtocol()}://${window.location.hostname}:${window.location.port}/websockets`;
|
||||
};
|
||||
|
||||
export function TRPCReactProvider({ children }: PropsWithChildren) {
|
||||
export function TRPCReactProvider({ children, demoReadOnly }: PropsWithChildren<{ demoReadOnly: boolean }>) {
|
||||
const { data: session } = useSession();
|
||||
const { logoutRedirectInProgress } = useAuthContext();
|
||||
const sessionQueryScope = getSessionQueryScope(session);
|
||||
@@ -81,7 +82,7 @@ export function TRPCReactProvider({ children }: PropsWithChildren) {
|
||||
currentScope={sessionQueryScope}
|
||||
onScopeChange={handleScopeChange}
|
||||
>
|
||||
<ScopedTRPCReactProvider>{children}</ScopedTRPCReactProvider>
|
||||
<ScopedTRPCReactProvider demoReadOnly={demoReadOnly}>{children}</ScopedTRPCReactProvider>
|
||||
</SessionQueryScopeGuard>
|
||||
);
|
||||
}
|
||||
@@ -102,7 +103,7 @@ const clearLegacyDashboardPersistence = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const ScopedTRPCReactProvider = ({ children }: PropsWithChildren) => {
|
||||
const ScopedTRPCReactProvider = ({ children, demoReadOnly }: PropsWithChildren<{ demoReadOnly: boolean }>) => {
|
||||
useEffect(clearLegacyDashboardPersistence, []);
|
||||
const wsClient = useMemo(
|
||||
() =>
|
||||
@@ -150,21 +151,26 @@ const ScopedTRPCReactProvider = ({ children }: PropsWithChildren) => {
|
||||
},
|
||||
});
|
||||
client.setQueryDefaults([["widget"]], {
|
||||
refetchInterval: queryCacheDefaultRefetchIntervalMs,
|
||||
refetchInterval: demoReadOnly ? false : queryCacheDefaultRefetchIntervalMs,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
});
|
||||
for (const queryDefaults of widgetQueryRefetchIntervals) {
|
||||
const policy: { refetchInterval?: number | false; staleTime?: number } = {};
|
||||
if (queryDefaults.intervalSeconds === null) policy.refetchInterval = false;
|
||||
if (typeof queryDefaults.intervalSeconds === "number") {
|
||||
if (demoReadOnly || queryDefaults.intervalSeconds === null) policy.refetchInterval = false;
|
||||
else if (typeof queryDefaults.intervalSeconds === "number") {
|
||||
policy.refetchInterval = queryDefaults.intervalSeconds * 1000;
|
||||
}
|
||||
if ("staleTimeSeconds" in queryDefaults) policy.staleTime = queryDefaults.staleTimeSeconds * 1000;
|
||||
client.setQueryDefaults(queryDefaults.queryKey, policy);
|
||||
}
|
||||
for (const { queryKey, ...policy } of dashboardSupportingQueryPolicies) {
|
||||
client.setQueryDefaults(queryKey, policy);
|
||||
client.setQueryDefaults(
|
||||
queryKey,
|
||||
demoReadOnly
|
||||
? { ...policy, refetchInterval: false, refetchOnWindowFocus: false, refetchOnReconnect: false }
|
||||
: policy,
|
||||
);
|
||||
}
|
||||
if (typeof window === "undefined") {
|
||||
client.getQueryCache().subscribe((event) => {
|
||||
@@ -239,7 +245,7 @@ const ScopedTRPCReactProvider = ({ children }: PropsWithChildren) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<DemoReadOnlyProvider value={demoReadOnly}>{children}</DemoReadOnlyProvider>
|
||||
</ReactQueryStreamedHydration>
|
||||
{process.env.NODE_ENV === "development" && <DevelopmentTools />}
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -167,7 +167,7 @@ export default async function Layout(props: {
|
||||
/>
|
||||
),
|
||||
(innerProps) => <JotaiProvider {...innerProps} />,
|
||||
(innerProps) => <TRPCReactProvider {...innerProps} />,
|
||||
(innerProps) => <TRPCReactProvider demoReadOnly={env.DEMO_MODE && env.DEMO_READ_ONLY} {...innerProps} />,
|
||||
(innerProps) => <DayJsLoader {...innerProps} />,
|
||||
(innerProps) => <NextIntlClientProvider {...innerProps} />,
|
||||
(innerProps) => (
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"./custom-api/component": "./src/custom-api/component.tsx",
|
||||
"./custom-api/custom-jsx-display": "./src/custom-api/custom-jsx-display.tsx",
|
||||
"./custom-api/extract-display-data": "./src/custom-api/extract-display-data.ts",
|
||||
"./demo-read-only": "./src/demo-read-only.tsx",
|
||||
"./assistant/context": "./src/assistant/context.tsx",
|
||||
"./catalog": "./src/catalog.ts",
|
||||
"./definition": "./src/definition.ts",
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { createByteChartAxisFormatters, formatPercent } from "./format";
|
||||
import { makeTooltipProps } from "./tooltip";
|
||||
import { useLiveStats } from "./use-live-stats";
|
||||
import { useDemoReadOnly } from "../../demo-read-only";
|
||||
|
||||
const CHART_HEIGHT = 180;
|
||||
const MEBIBYTE = 1024 * 1024;
|
||||
@@ -97,9 +98,10 @@ export function BeszelStatsView({
|
||||
);
|
||||
const showDocker = visibility.dockerCpu || visibility.dockerMemory || visibility.dockerNetwork;
|
||||
const isLive = timePeriod === "1m";
|
||||
const demoReadOnly = useDemoReadOnly();
|
||||
const historicalQuery = clientApi.widget.beszel.getSystemStats.useQuery(
|
||||
{ integrationIds, systemId, timePeriod, includeDocker: showDocker },
|
||||
{ refetchInterval: isLive ? false : 5_000, enabled: !isLive && systemId !== "" },
|
||||
{ refetchInterval: isLive || demoReadOnly ? false : 5_000, enabled: !isLive && systemId !== "" },
|
||||
);
|
||||
const historicalData = isLive ? historicalQuery.data : getUsableWidgetQueryData(historicalQuery);
|
||||
const { data: liveData, error: liveError } = useLiveStats(integrationIds, systemId, isLive && systemId !== "");
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Link, zoomCompensatedSize } from "@homarr/ui";
|
||||
|
||||
import { WidgetQueryLoadingState } from "../common/query-state-indicator";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { useDemoReadOnly } from "../demo-read-only";
|
||||
import { isLegacyCustomWidgetMigrationError, isTerminalCustomWidgetDefinitionError } from "./migration-state";
|
||||
|
||||
const CustomJsxDisplay = dynamic(() => import("./custom-jsx-display"), { ssr: false });
|
||||
@@ -23,6 +24,7 @@ export default function CustomApiWidget({
|
||||
}: WidgetComponentProps<"customApi">) {
|
||||
const t = useI18n("widget.customApi");
|
||||
const tCustomJsx = useI18n("widget.customApi.customJsx");
|
||||
const demoReadOnly = useDemoReadOnly();
|
||||
const { data: session } = useSession();
|
||||
const isAdmin = session?.user.permissions.includes("admin") ?? false;
|
||||
const definitionId = options.definitionId;
|
||||
@@ -33,8 +35,9 @@ export default function CustomApiWidget({
|
||||
{
|
||||
enabled: Boolean(itemId) && Boolean(definitionId),
|
||||
refetchInterval: (currentQuery) =>
|
||||
isTerminalCustomWidgetDefinitionError(currentQuery.state.error) ? false : intervalMs,
|
||||
retry: (failureCount, error) => !isTerminalCustomWidgetDefinitionError(error) && failureCount < 3,
|
||||
demoReadOnly || isTerminalCustomWidgetDefinitionError(currentQuery.state.error) ? false : intervalMs,
|
||||
retry: (failureCount, error) =>
|
||||
!demoReadOnly && !isTerminalCustomWidgetDefinitionError(error) && failureCount < 3,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
const DemoReadOnlyContext = createContext(false);
|
||||
|
||||
export const DemoReadOnlyProvider = DemoReadOnlyContext.Provider;
|
||||
|
||||
export const useDemoReadOnly = () => useContext(DemoReadOnlyContext);
|
||||
@@ -22,6 +22,7 @@ import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { useDemoReadOnly } from "../demo-read-only";
|
||||
import { useWidgetRuntimeActions } from "../runtime-hooks";
|
||||
import { statsEntriesSchema } from "./config";
|
||||
import { StatsTable } from "./table";
|
||||
@@ -41,6 +42,7 @@ export default function StatsWidget({
|
||||
displayScale = 1,
|
||||
}: WidgetComponentProps<"stats">) {
|
||||
const board = useRequiredBoard();
|
||||
const demoReadOnly = useDemoReadOnly();
|
||||
const advanced = displayMode === "advanced";
|
||||
const t = useI18n("widget.stats");
|
||||
const utils = clientApi.useUtils();
|
||||
@@ -81,8 +83,8 @@ export default function StatsWidget({
|
||||
{ integrationId },
|
||||
{
|
||||
staleTime: 30_000,
|
||||
refetchInterval: 60_000,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchInterval: demoReadOnly ? false : 60_000,
|
||||
refetchOnWindowFocus: !demoReadOnly,
|
||||
refetchIntervalInBackground: false,
|
||||
retry: false,
|
||||
},
|
||||
@@ -153,13 +155,13 @@ export default function StatsWidget({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (document.hidden) return;
|
||||
if (demoReadOnly || document.hidden) return;
|
||||
for (const [index, integrationId] of visibleIds.entries()) {
|
||||
const snapshot = snapshots[index];
|
||||
if (!snapshot?.data || snapshot.error) continue;
|
||||
if (snapshot.data.updatedAt === null && snapshot.data.retryAt <= Date.now()) void refresh(integrationId, false);
|
||||
}
|
||||
}, [visibleIds, snapshots, refresh]);
|
||||
}, [demoReadOnly, visibleIds, snapshots, refresh]);
|
||||
|
||||
const tableEntries = advanced
|
||||
? ids.flatMap((id) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import classes from "./component.module.css";
|
||||
import { UmamiEventsContent } from "./umami-events-content";
|
||||
import { getUmamiLayout } from "./umami-layout";
|
||||
import { UmamiTopPagesContent, UmamiTopReferrersContent } from "./umami-top-list";
|
||||
import { formatTimeFrameLabel, formatXLabel, umamiQueryOptions } from "./umami-utils";
|
||||
import { formatTimeFrameLabel, formatXLabel, useUmamiQueryOptions } from "./umami-utils";
|
||||
import type { TimeFrame } from "./umami-utils";
|
||||
import { getUsableWidgetQueryData, isInitialWidgetQueryPending } from "../common/query-state";
|
||||
import { WidgetQueryLoadingState } from "../common/query-state-indicator";
|
||||
@@ -48,6 +48,7 @@ export function UmamiContent({
|
||||
}: UmamiContentProps) {
|
||||
const t = useI18n("widget.umami");
|
||||
const locale = useCurrentIntlLocale();
|
||||
const umamiQueryOptions = useUmamiQueryOptions();
|
||||
const tickColor = "var(--mantine-color-dimmed)";
|
||||
|
||||
const statsQuery = clientApi.widget.umami.getVisitorStats.useQuery(
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { UmamiMetricItem } from "@homarr/integrations/types";
|
||||
import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client";
|
||||
import classes from "./component.module.css";
|
||||
|
||||
import { umamiQueryOptions } from "./umami-utils";
|
||||
import { useUmamiQueryOptions } from "./umami-utils";
|
||||
import { getUsableWidgetQueryData } from "../common/query-state";
|
||||
|
||||
interface UmamiTopListProps {
|
||||
@@ -19,6 +19,7 @@ interface UmamiTopListProps {
|
||||
|
||||
export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) {
|
||||
const t = useI18n("widget.umami");
|
||||
const umamiQueryOptions = useUmamiQueryOptions();
|
||||
const data =
|
||||
getUsableWidgetQueryData(
|
||||
clientApi.widget.umami.getTopPages.useQuery(
|
||||
@@ -31,6 +32,7 @@ export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, lim
|
||||
|
||||
export function UmamiTopReferrersContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) {
|
||||
const t = useI18n("widget.umami");
|
||||
const umamiQueryOptions = useUmamiQueryOptions();
|
||||
const data =
|
||||
getUsableWidgetQueryData(
|
||||
clientApi.widget.umami.getTopReferrers.useQuery(
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import type { useI18n } from "@homarr/translation/client";
|
||||
import { useDemoReadOnly } from "../demo-read-only";
|
||||
|
||||
import { timeFrameValues } from "./index";
|
||||
|
||||
export const umamiQueryOptions = { refetchInterval: 60_000 } as const;
|
||||
export const useUmamiQueryOptions = (): { refetchInterval: number | false } => ({
|
||||
refetchInterval: useDemoReadOnly() ? false : 60_000,
|
||||
});
|
||||
|
||||
export const EVENT_COLORS = ["blue.5", "orange.5", "green.5", "red.5", "violet.5", "teal.5", "yellow.5", "pink.5"];
|
||||
|
||||
|
||||
Reference in new issue
Block a user