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({