From ebd384c62dcff0e0ec4b27097f4b9baee83a0bdb Mon Sep 17 00:00:00 2001 From: Reiss Cashmore Date: Tue, 31 Oct 2023 14:19:57 +0000 Subject: [PATCH] Feature: iFrame widget (#2261) --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/iframe.md | 35 ++++++++++++++++++++ mkdocs.yml | 1 + src/utils/config/service-helpers.js | 17 ++++++++++ src/widgets/components.js | 1 + src/widgets/iframe/component.jsx | 50 +++++++++++++++++++++++++++++ src/widgets/iframe/widget.js | 3 ++ tailwind.config.js | 4 +++ 7 files changed, 111 insertions(+) create mode 100644 docs/widgets/services/iframe.md create mode 100644 src/widgets/iframe/component.jsx create mode 100644 src/widgets/iframe/widget.js diff --git a/docs/widgets/services/iframe.md b/docs/widgets/services/iframe.md new file mode 100644 index 000000000..6cd969e42 --- /dev/null +++ b/docs/widgets/services/iframe.md @@ -0,0 +1,35 @@ +--- +title: iFrame +Description: Add a custom iFrame Widget +--- + +A basic iFrame widget to show external content, see the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) for more details about some of the options. + +!!! warning + + Requests made via the iFrame widget are inherently **not proxied** as they are made from the browser itself. + +## Basic Example + +```yaml +widget: + type: iframe + name: myIframe + src: http://example.com +``` + +## Full Example + +```yaml +widget: + type: iframe + name: myIframe + src: http://example.com + classes: h-60 sm:h-60 md:h-60 lg:h-60 xl:h-60 2xl:h-72 # optional, use tailwind height classes, see https://tailwindcss.com/docs/height + referrerPolicy: same-origin # optional, no default + allowPolicy: autoplay fullscreen gamepad # optional, no default + allowFullscreen: false # optional, default: true + loadingStrategy: eager # optional, default: eager + allowScrolling: no # optional, default: yes + refreshInterval: 2000 # optional, no default +``` diff --git a/mkdocs.yml b/mkdocs.yml index 4dc382f9c..f356c1502 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,7 @@ nav: - widgets/services/healthchecks.md - widgets/services/homeassistant.md - widgets/services/homebridge.md + - widgets/services/iframe.md - widgets/services/immich.md - widgets/services/jackett.md - widgets/services/jdownloader.md diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 04f9e883d..8da39e18a 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -366,6 +366,13 @@ export function cleanServiceGroups(groups) { firstDayInWeek, view, maxEvents, + src, // iframe widget + classes, + referrerPolicy, + allowPolicy, + allowFullscreen, + loadingStrategy, + allowScrolling, } = cleanedService.widget; let fieldsList = fields; @@ -413,6 +420,16 @@ export function cleanServiceGroups(groups) { if (app) cleanedService.widget.app = app; if (podSelector) cleanedService.widget.podSelector = podSelector; } + if (type === "iframe") { + if (src) cleanedService.widget.src = src; + if (classes) cleanedService.widget.classes = classes; + if (referrerPolicy) cleanedService.widget.referrerPolicy = referrerPolicy; + if (allowPolicy) cleanedService.widget.allowPolicy = allowPolicy; + if (allowFullscreen) cleanedService.widget.allowFullscreen = allowFullscreen; + if (loadingStrategy) cleanedService.widget.loadingStrategy = loadingStrategy; + if (allowScrolling) cleanedService.widget.allowScrolling = allowScrolling; + if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval; + } if (["opnsense", "pfsense"].includes(type)) { if (wan) cleanedService.widget.wan = wan; } diff --git a/src/widgets/components.js b/src/widgets/components.js index 9d311b977..99da81ea5 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -15,6 +15,7 @@ const components = { channelsdvrserver: dynamic(() => import("./channelsdvrserver/component")), cloudflared: dynamic(() => import("./cloudflared/component")), coinmarketcap: dynamic(() => import("./coinmarketcap/component")), + iframe: dynamic(() => import("./iframe/component")), customapi: dynamic(() => import("./customapi/component")), deluge: dynamic(() => import("./deluge/component")), diskstation: dynamic(() => import("./diskstation/component")), diff --git a/src/widgets/iframe/component.jsx b/src/widgets/iframe/component.jsx new file mode 100644 index 000000000..21075dbe8 --- /dev/null +++ b/src/widgets/iframe/component.jsx @@ -0,0 +1,50 @@ +import { useState, useEffect } from "react"; +import classNames from "classnames"; + +import Container from "components/services/widget/container"; + +export default function Component({ service }) { + const [refreshTimer, setRefreshTimer] = useState(0); + + const { widget } = service; + + useEffect(() => { + if (widget?.refreshInterval) { + setInterval( + () => setRefreshTimer(refreshTimer + 1), + widget?.refreshInterval < 1000 ? 1000 : widget?.refreshInterval, + ); + } + }, [refreshTimer, widget?.refreshInterval]); + + const scrollingDisableStyle = widget?.allowScrolling === "no" ? { pointerEvents: "none", overflow: "hidden" } : {}; + + const classes = widget?.classes || "h-60 sm:h-60 md:h-60 lg:h-60 xl:h-60 2xl:h-72"; + + return ( + +
+