diff --git a/apps/docs/docs/advanced/command-line/index.mdx b/apps/docs/docs/advanced/command-line/index.mdx index 9acbdc333..0e77162a2 100644 --- a/apps/docs/docs/advanced/command-line/index.mdx +++ b/apps/docs/docs/advanced/command-line/index.mdx @@ -36,7 +36,7 @@ pnpm cli harness setup feature-board The command builds `homarr:feature-board`, starts a container with a persistent data volume, enables demo mode, and keeps demo mutations writable (`DEMO_READ_ONLY=false`). Docker assigns a free host port by default; use the URL printed by the command or query it later: -The demo board includes every widget kind. Integration-backed widgets use the bundled mock integration; media widgets use bundled same-origin demo assets. +The demo board includes every widget kind. Integration-backed widgets use the bundled mock integration; media widgets use public stock assets. ```sh pnpm cli harness port feature-board diff --git a/apps/docs/docs/management/api/index.mdx b/apps/docs/docs/management/api/index.mdx index 4e3693d83..8a6e407c0 100644 --- a/apps/docs/docs/management/api/index.mdx +++ b/apps/docs/docs/management/api/index.mdx @@ -144,10 +144,6 @@ Docker or Podman endpoints from targeting the wrong service. Docker environment, or pass one or more IDs returned by `docker.getEndpoints` to query only those Dockerode clients. The Docker widget uses the same behavior for its environment selector. -`docker.refreshInventory` always refreshes the current replica and returns `scope: "published"` when Redis accepts the -non-persistent refresh event, or `scope: "local"` when publication fails. Published scope does not confirm that every replica -processed the event. A local result is partial success: retry after restoring Redis to notify the other replicas. - ## Partial integration failures Multi-integration widget queries keep data from integrations that remain available. Calendar and media-server results include diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx index 6b54c06a2..d69fc7dc4 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx @@ -41,7 +41,7 @@ import type { UrlTemplateMode } from "@homarr/definitions"; import { getIntegrationName, invariantTechnicalLabels } from "@homarr/definitions"; import { useModalAction } from "@homarr/modals"; import { AddDockerAppToHomarr } from "@homarr/modals-collection"; -import { showErrorNotification, showWarningNotification } from "@homarr/notifications"; +import { showErrorNotification } from "@homarr/notifications"; import { normalizeServiceUrl, ServiceUrlTemplate } from "@homarr/onboarding"; import { useI18n } from "@homarr/translation/client"; import { Link } from "@homarr/ui"; @@ -69,18 +69,12 @@ export const DockerReconciliation = ({ defaultServerOrigin }: { defaultServerOri const reconciliation = clientApi.docker.reconcileServices.useQuery(); const health = clientApi.docker.getServiceHealth.useQuery(undefined, { enabled: isOpen }); const refreshInventory = clientApi.docker.refreshInventory.useMutation({ - async onSuccess(result) { + async onSuccess() { await Promise.all([ utils.docker.getContainers.invalidate(), utils.docker.reconcileServices.invalidate(), utils.docker.getServiceHealth.invalidate(), ]); - if (result.scope === "local") { - showWarningNotification({ - title: tDockerAction("refresh.notification.warning.title"), - message: tDockerAction("refresh.notification.warning.message"), - }); - } }, onError() { showErrorNotification({ diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx index 7e96ac981..716ca1c1c 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx @@ -25,7 +25,7 @@ import type { ContainerState, DockerEndpointCapability } from "@homarr/docker"; import { containerStateColorMap, cpuUsageColor, memoryUsageColor, safeValue } from "@homarr/docker/shared"; import { useModalAction } from "@homarr/modals"; import { AddDockerAppToHomarr, useDockerContainerRemovalConfirmation } from "@homarr/modals-collection"; -import { showErrorNotification, showSuccessNotification, showWarningNotification } from "@homarr/notifications"; +import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import type { ScopedTranslationFunction } from "@homarr/translation"; import { useI18n } from "@homarr/translation/client"; import type { TablerIcon } from "@homarr/ui"; @@ -199,19 +199,12 @@ export function DockerTable({ initialData }: DockerTableProps) { refetchOnMount: false, }); const refreshInventory = clientApi.docker.refreshInventory.useMutation({ - async onSuccess(result) { + async onSuccess() { await Promise.all([ utils.docker.getContainers.invalidate(), utils.docker.reconcileServices.invalidate(), utils.docker.getServiceHealth.invalidate(), ]); - if (result.scope === "local") { - showWarningNotification({ - title: tDocker("action.refresh.notification.warning.title"), - message: tDocker("action.refresh.notification.warning.message"), - }); - return; - } showSuccessNotification({ title: tDocker("action.refresh.notification.success.title"), message: tDocker("action.refresh.notification.success.message"), diff --git a/apps/nextjs/src/app/api/demo-assets/overview/route.ts b/apps/nextjs/src/app/api/demo-assets/overview/route.ts deleted file mode 100644 index 487caf6c4..000000000 --- a/apps/nextjs/src/app/api/demo-assets/overview/route.ts +++ /dev/null @@ -1,56 +0,0 @@ -const overview = ` - - - - - - - -
-
H
-

Your home, at a glance.

-

Bring services, media, infrastructure, notes and everyday tools together on one practical dashboard.

-
59 widgetsMock data includedBuilt for self-hosters
-
- -`; - -export const GET = () => - new Response(overview, { - headers: { - "Cache-Control": "public, max-age=3600", - "Content-Type": "text/html; charset=utf-8", - }, - }); diff --git a/apps/nextjs/src/app/api/demo-assets/video/file.mp4/route.ts b/apps/nextjs/src/app/api/demo-assets/video/file.mp4/route.ts deleted file mode 100644 index 0ced33071..000000000 --- a/apps/nextjs/src/app/api/demo-assets/video/file.mp4/route.ts +++ /dev/null @@ -1 +0,0 @@ -export { GET } from "../route"; diff --git a/apps/nextjs/src/app/api/demo-assets/video/route.ts b/apps/nextjs/src/app/api/demo-assets/video/route.ts deleted file mode 100644 index 47c74d125..000000000 --- a/apps/nextjs/src/app/api/demo-assets/video/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -const videoBase64 = - "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"; - -export const GET = () => - new Response(Buffer.from(videoBase64, "base64"), { - headers: { - "Cache-Control": "public, max-age=3600", - "Content-Type": "video/mp4", - }, - }); diff --git a/packages/api/src/router/docker/docker-router.ts b/packages/api/src/router/docker/docker-router.ts index 7c0e549f4..910d16131 100644 --- a/packages/api/src/router/docker/docker-router.ts +++ b/packages/api/src/router/docker/docker-router.ts @@ -4,8 +4,6 @@ import { z } from "zod/v4"; import type { ContainerState, DockerContainerTarget, Port } from "@homarr/docker"; import { DockerSingleton } from "@homarr/docker"; -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { createSubPubChannel } from "@homarr/redis"; import { dockerContainersRequestHandler, findDockerContainerAsync, @@ -24,34 +22,6 @@ const dockerContainerTargetSchema = z.object({ id: z.string().min(1), }); const dockerContainerTargetsSchema = z.array(dockerContainerTargetSchema).min(1).max(100); -const logger = createLogger({ module: "dockerRouter" }); - -const resetDockerInventory = () => { - DockerSingleton.reset(); - dockerContainersRequestHandler.invalidateCache(); -}; - -const dockerInventoryRefreshChannel = createSubPubChannel<{ requestedAt: number }>("docker-inventory-refresh", { - persist: false, -}); -const isRedisDisabled = () => Boolean(process.env.CI) || Boolean(process.env.DISABLE_REDIS_LOGS); -try { - dockerInventoryRefreshChannel.subscribe(resetDockerInventory); -} catch (error) { - if (!isRedisDisabled()) throw error; -} - -const publishDockerInventoryRefreshAsync = async () => { - try { - await dockerInventoryRefreshChannel.publishAsync({ requestedAt: Date.now() }); - return true; - } catch (error) { - if (!isRedisDisabled()) { - logger.warn(new Error("Failed to publish Docker inventory refresh", { cause: error })); - } - return false; - } -}; const normalizeGetContainersInput = (input: { endpointIds?: string[] } | undefined) => { if (!input?.endpointIds || input.endpointIds.length === 0) return {}; @@ -87,15 +57,13 @@ export const dockerRouter = createTRPCRouter({ mcp: { enabled: true, description: - "Invalidate the local Docker inventory and publish a non-persistent Redis refresh event. Returns scope 'published' when Redis accepts the event or 'local' when only this replica is refreshed. Requires admin permission.", + "Invalidate the cached Docker inventory so the next Docker query performs a fresh user-requested discovery. Requires admin permission.", }, }) .concat(dockerMiddleware()) - .mutation(async () => { - resetDockerInventory(); - const propagated = await publishDockerInventoryRefreshAsync(); - if (propagated) return { scope: "published" as const }; - return { scope: "local" as const }; + .mutation(() => { + DockerSingleton.reset(); + dockerContainersRequestHandler.invalidateCache(); }), getContainers: permissionRequiredProcedure .requiresPermission("admin") diff --git a/packages/api/src/router/test/docker/docker-router.spec.ts b/packages/api/src/router/test/docker/docker-router.spec.ts index f3573beb1..504a8c068 100644 --- a/packages/api/src/router/test/docker/docker-router.spec.ts +++ b/packages/api/src/router/test/docker/docker-router.spec.ts @@ -15,17 +15,6 @@ const findDockerContainerAsyncMock = vi.hoisted(() => vi.fn()); const hasDockerEndpointCapabilityMock = vi.hoisted(() => vi.fn(() => true)); const invalidateDockerCacheMock = vi.hoisted(() => vi.fn()); const resetDockerSingletonMock = vi.hoisted(() => vi.fn()); -const dockerInventoryRefreshChannelMock = vi.hoisted(() => { - let subscriber: (() => void) | undefined; - return { - publishAsync: vi.fn(async () => undefined), - subscribe: vi.fn((callback: () => void) => { - subscriber = callback; - return () => undefined; - }), - trigger: () => subscriber?.(), - }; -}); const getDockerDataAsyncMock = vi.hoisted(() => vi.fn< () => Promise<{ @@ -77,9 +66,6 @@ vi.mock("@homarr/docker", () => ({ reset: resetDockerSingletonMock, }, })); -vi.mock("@homarr/redis", () => ({ - createSubPubChannel: () => dockerInventoryRefreshChannelMock, -})); vi.mock("@homarr/request-handler/docker", () => ({ findDockerContainerAsync: findDockerContainerAsyncMock, hasDockerEndpointCapability: hasDockerEndpointCapabilityMock, @@ -517,12 +503,6 @@ test("invalidates the shared Docker inventory on explicit refresh", async () => expect(invalidateDockerCacheMock).toHaveBeenCalledOnce(); expect(resetDockerSingletonMock).toHaveBeenCalledOnce(); - expect(dockerInventoryRefreshChannelMock.publishAsync).toHaveBeenCalledOnce(); - - dockerInventoryRefreshChannelMock.trigger(); - - expect(invalidateDockerCacheMock).toHaveBeenCalledTimes(2); - expect(resetDockerSingletonMock).toHaveBeenCalledTimes(2); }); test("projects persisted service layers without inventing runtime health", async () => { diff --git a/packages/db/migrations/seed.ts b/packages/db/migrations/seed.ts index 904800bc7..3e4cdebf2 100644 --- a/packages/db/migrations/seed.ts +++ b/packages/db/migrations/seed.ts @@ -805,7 +805,10 @@ const buildDemoWidgets = (appIds: string[], customWidgetDefinitionId: string): D width: 4, height: 3, needsIntegration: false, - options: { embedUrl: "/api/demo-assets/overview", allowScrolling: true }, + options: { + embedUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg", + allowScrolling: false, + }, }, { kind: "video", @@ -815,7 +818,7 @@ const buildDemoWidgets = (appIds: string[], customWidgetDefinitionId: string): D height: 3, needsIntegration: false, options: { - feedUrl: "/api/demo-assets/video/file.mp4", + feedUrl: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4", hasAutoPlay: false, isMuted: true, hasControls: true, diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index afc3c1338..59e09e437 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -6644,10 +6644,6 @@ "title": "Containers refreshed", "message": "You are now viewing the most recent data" }, - "warning": { - "title": "Only this Homarr instance was refreshed", - "message": "Redis did not accept the refresh event, so other Homarr instances may still show older Docker data" - }, "error": { "title": "Containers not refreshed", "message": "Something went wrong while refreshing the containers" diff --git a/packages/widgets/src/docker/component.tsx b/packages/widgets/src/docker/component.tsx index d3b57d12c..4004dd52e 100644 --- a/packages/widgets/src/docker/component.tsx +++ b/packages/widgets/src/docker/component.tsx @@ -42,7 +42,7 @@ import type { ContainerState, DockerEndpointCapability } from "@homarr/docker"; import { containerStateColorMap, cpuUsageColor, memoryUsageColor, safeValue } from "@homarr/docker/shared"; import { useModalAction } from "@homarr/modals"; import { AddDockerAppToHomarr, useDockerContainerRemovalConfirmation } from "@homarr/modals-collection"; -import { showErrorNotification, showSuccessNotification, showWarningNotification } from "@homarr/notifications"; +import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useI18n } from "@homarr/translation/client"; import { zoomCompensatedSize } from "@homarr/ui"; @@ -285,18 +285,12 @@ export default function DockerWidget({ const data = getUsableWidgetQueryData(containersQuery); const { isFetching } = containersQuery; const refreshInventory = clientApi.docker.refreshInventory.useMutation({ - async onSuccess(result) { + async onSuccess() { await Promise.all([ utils.docker.getContainers.invalidate(), utils.docker.reconcileServices.invalidate(), utils.docker.getServiceHealth.invalidate(), ]); - if (result.scope === "local") { - showWarningNotification({ - title: t("action.refresh.notification.warning.title"), - message: t("action.refresh.notification.warning.message"), - }); - } }, onError() { showErrorNotification({