From 245a47796514eb46afb1c3e30850d6adc0a0350a Mon Sep 17 00:00:00 2001 From: ajnart Date: Mon, 24 Aug 2026 12:07:07 +0200 Subject: [PATCH] refactor: simplify demo media --- .../docs/docs/advanced/command-line/index.mdx | 2 +- .../src/app/api/demo-assets/overview/route.ts | 56 ------------------- .../api/demo-assets/video/file.mp4/route.ts | 1 - .../src/app/api/demo-assets/video/route.ts | 10 ---- packages/db/migrations/seed.ts | 7 ++- packages/integrations/src/mock/widget-data.ts | 9 +-- packages/widgets/src/iframe/component.spec.ts | 5 +- packages/widgets/src/iframe/component.tsx | 8 +-- packages/widgets/src/iframe/index.ts | 4 +- packages/widgets/src/iframe/safe-url.ts | 10 ---- 10 files changed, 14 insertions(+), 98 deletions(-) delete mode 100644 apps/nextjs/src/app/api/demo-assets/overview/route.ts delete mode 100644 apps/nextjs/src/app/api/demo-assets/video/file.mp4/route.ts delete mode 100644 apps/nextjs/src/app/api/demo-assets/video/route.ts delete mode 100644 packages/widgets/src/iframe/safe-url.ts diff --git a/apps/docs/docs/advanced/command-line/index.mdx b/apps/docs/docs/advanced/command-line/index.mdx index c6b8ce69c..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 available widget kind. Integration-backed widgets use the bundled mock integration, while network or media examples use deterministic demo-safe sources where possible. +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/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 401e97564..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=31536000, immutable", - "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 c4ef08adb..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=31536000, immutable", - "Content-Type": "video/mp4", - }, - }); 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/integrations/src/mock/widget-data.ts b/packages/integrations/src/mock/widget-data.ts index ea673152f..5df6f8dcd 100644 --- a/packages/integrations/src/mock/widget-data.ts +++ b/packages/integrations/src/mock/widget-data.ts @@ -22,11 +22,6 @@ import type { VpnSummary } from "../interfaces/vpn-summary/vpn-summary-types"; const demoTimestamp = "2026-08-24T12:00:00.000Z"; -const demoPhoto = (label: string, color: string) => { - const svg = `${label}`; - return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; -}; - const anchorNote: AnchorNote = { id: "homarr-demo-note", title: "Homarr demo runbook", @@ -168,7 +163,7 @@ export const mockWidgetData = { fileCreatedAt: demoTimestamp, updatedAt: demoTimestamp, type: "IMAGE", - publicLink: demoPhoto("Paris weekend", "#6f4cff"), + publicLink: "https://images.unsplash.com/photo-1502602898657-3e91760cbb34?auto=format&fit=crop&w=1200&q=80", }, { id: "demo-photo-2", @@ -177,7 +172,7 @@ export const mockWidgetData = { fileCreatedAt: demoTimestamp, updatedAt: demoTimestamp, type: "IMAGE", - publicLink: demoPhoto("Alpine trails", "#198f83"), + publicLink: "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1200&q=80", }, ], } satisfies ImmichAlbum, diff --git a/packages/widgets/src/iframe/component.spec.ts b/packages/widgets/src/iframe/component.spec.ts index 5ec11f07d..a5f3b5835 100644 --- a/packages/widgets/src/iframe/component.spec.ts +++ b/packages/widgets/src/iframe/component.spec.ts @@ -18,11 +18,8 @@ describe("iframe policy", () => { expect(getSandboxFlags(permissions)).toContain("allow-modals"); }); - it("accepts HTTP URLs and same-origin paths", () => { + it("accepts only HTTP protocols", () => { expect(isSupportedProtocol("https://example.com")).toBe(true); - expect(isSupportedProtocol("/api/demo-assets/overview")).toBe(true); - expect(isSupportedProtocol("//evil.example")).toBe(false); - expect(isSupportedProtocol("/\\evil.example")).toBe(false); expect(isSupportedProtocol("javascript:alert(1)")).toBe(false); expect(isSupportedProtocol("not a url")).toBe(false); }); diff --git a/packages/widgets/src/iframe/component.tsx b/packages/widgets/src/iframe/component.tsx index 23a5134bd..c66343927 100644 --- a/packages/widgets/src/iframe/component.tsx +++ b/packages/widgets/src/iframe/component.tsx @@ -7,15 +7,13 @@ import { objectEntries } from "@homarr/common"; import { useI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; +import { getSafeApplicationUrl } from "../common/application-url"; import classes from "./component.module.css"; -import { getSafeIframeUrl } from "./safe-url"; - -export { getSafeIframeUrl } from "./safe-url"; export default function IFrameWidget({ options, isEditMode }: WidgetComponentProps<"iframe">) { const t = useI18n("widget.iframe"); const { embedUrl, allowScrolling, ...permissions } = options; - const safeEmbedUrl = getSafeIframeUrl(embedUrl); + const safeEmbedUrl = getSafeApplicationUrl(embedUrl); const allowedPermissions = getAllowedPermissions(permissions); const sandboxFlags = getSandboxFlags(permissions); @@ -47,7 +45,7 @@ export default function IFrameWidget({ options, isEditMode }: WidgetComponentPro const supportedProtocols = ["http", "https"]; export const isSupportedProtocol = (url: string) => { - return getSafeIframeUrl(url) !== undefined; + return getSafeApplicationUrl(url) !== undefined; }; const NoUrl = () => { diff --git a/packages/widgets/src/iframe/index.ts b/packages/widgets/src/iframe/index.ts index 70ad9056d..981ab180c 100644 --- a/packages/widgets/src/iframe/index.ts +++ b/packages/widgets/src/iframe/index.ts @@ -1,8 +1,8 @@ import { IconBrowser, IconExternalLink } from "@tabler/icons-react"; import { createWidgetDefinition } from "../definition"; +import { getSafeApplicationUrl } from "../common/application-url"; import { optionsBuilder } from "../options"; -import { getSafeIframeUrl } from "./safe-url"; const createOptions = () => optionsBuilder.from((factory) => ({ @@ -23,7 +23,7 @@ export const { definition, componentLoader } = createWidgetDefinition("iframe", supportsAdvancedFocus: false, icon: IconBrowser, contextActions: ({ options }) => { - const embedUrl = getSafeIframeUrl(options.embedUrl); + const embedUrl = getSafeApplicationUrl(options.embedUrl); return embedUrl ? [ { diff --git a/packages/widgets/src/iframe/safe-url.ts b/packages/widgets/src/iframe/safe-url.ts deleted file mode 100644 index 2837bc9ae..000000000 --- a/packages/widgets/src/iframe/safe-url.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { getSafeAppHref, getSafeApplicationUrl } from "@homarr/common"; - -export const getSafeIframeUrl = (url: unknown) => { - const applicationUrl = getSafeApplicationUrl(url); - if (applicationUrl) return applicationUrl; - - const relativeUrl = getSafeAppHref(url); - if (relativeUrl?.startsWith("/") && !relativeUrl.startsWith("//")) return relativeUrl; - return undefined; -};