diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index e0502488b..c2c80ddb2 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -55,6 +55,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Komga](komga.md) - [Kopia](kopia.md) - [Lidarr](lidarr.md) +- [Linkwarden](linkwarden.md) - [Mastodon](mastodon.md) - [Mealie](mealie.md) - [Medusa](medusa.md) diff --git a/docs/widgets/services/linkwarden.md b/docs/widgets/services/linkwarden.md new file mode 100644 index 000000000..bef196a99 --- /dev/null +++ b/docs/widgets/services/linkwarden.md @@ -0,0 +1,15 @@ +--- +title: Linkwarden +description: Linkwarden Widget Configuration +--- + +Learn more about [Linkwarden](https://linkwarden.app/). + +Allowed fields: `["links", "collections", "tags"]`. + +```yaml +widget: + type: linkwarden + url: http://linkwarden.host.or.ip + key: myApiKeyHere # On your Linkwarden install, go to Settings > Access Tokens. Generate a token. +``` diff --git a/mkdocs.yml b/mkdocs.yml index 4d6524b31..d48cc35e0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,6 +80,7 @@ nav: - widgets/services/komga.md - widgets/services/kopia.md - widgets/services/lidarr.md + - widgets/services/linkwarden.md - widgets/services/mastodon.md - widgets/services/mealie.md - widgets/services/medusa.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 28c548953..cd9b2c143 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -905,5 +905,10 @@ "cameras": "Cameras", "uptime": "Uptime", "version": "Version" + }, + "linkwarden": { + "links": "Links", + "collections": "Collections", + "tags": "Tags" } } diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 425228023..870d5b155 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -35,9 +35,16 @@ export default async function credentialedProxyHandler(req, res, map) { } else if (widget.type === "gotify") { headers["X-gotify-Key"] = `${widget.key}`; } else if ( - ["authentik", "cloudflared", "ghostfolio", "mealie", "tailscale", "tandoor", "pterodactyl"].includes( - widget.type, - ) + [ + "authentik", + "cloudflared", + "ghostfolio", + "linkwarden", + "mealie", + "tailscale", + "tandoor", + "pterodactyl", + ].includes(widget.type) ) { headers.Authorization = `Bearer ${widget.key}`; } else if (widget.type === "truenas") { diff --git a/src/widgets/components.js b/src/widgets/components.js index 341f5211d..159202421 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -54,6 +54,7 @@ const components = { komga: dynamic(() => import("./komga/component")), kopia: dynamic(() => import("./kopia/component")), lidarr: dynamic(() => import("./lidarr/component")), + linkwarden: dynamic(() => import("./linkwarden/component")), mastodon: dynamic(() => import("./mastodon/component")), mealie: dynamic(() => import("./mealie/component")), medusa: dynamic(() => import("./medusa/component")), diff --git a/src/widgets/linkwarden/component.jsx b/src/widgets/linkwarden/component.jsx new file mode 100644 index 000000000..e74a90a88 --- /dev/null +++ b/src/widgets/linkwarden/component.jsx @@ -0,0 +1,55 @@ +import React, { useState, useEffect } from "react"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { widget } = service; + + const [stats, setStats] = useState({ + totalLinks: null, + collections: { total: null }, + tags: { total: null }, + }); + + const { data: collectionsStatsData, error: collectionsStatsError } = useWidgetAPI(widget, "collections"); + const { data: tagsStatsData, error: tagsStatsError } = useWidgetAPI(widget, "tags"); + + useEffect(() => { + if (collectionsStatsData?.response && tagsStatsData?.response) { + setStats({ + // eslint-disable-next-line no-underscore-dangle + totalLinks: collectionsStatsData.response.reduce((sum, collection) => sum + (collection._count?.links || 0), 0), + collections: { + total: collectionsStatsData.response.length, + }, + tags: { + total: tagsStatsData.response.length, + }, + }); + } + }, [collectionsStatsData, tagsStatsData]); + + if (collectionsStatsError || tagsStatsError) { + return ; + } + + if (!tagsStatsData || !collectionsStatsData) { + return ( + + + + + + ); + } + + return ( + + + + + + ); +} diff --git a/src/widgets/linkwarden/widget.js b/src/widgets/linkwarden/widget.js new file mode 100644 index 000000000..799296f2b --- /dev/null +++ b/src/widgets/linkwarden/widget.js @@ -0,0 +1,17 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api/v1/{endpoint}", + proxyHandler: credentialedProxyHandler, + + mappings: { + collections: { + endpoint: "collections", + }, + tags: { + endpoint: "tags", + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index a72a4126d..684e2b7f7 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -46,6 +46,7 @@ import kavita from "./kavita/widget"; import komga from "./komga/widget"; import kopia from "./kopia/widget"; import lidarr from "./lidarr/widget"; +import linkwarden from "./linkwarden/widget"; import mastodon from "./mastodon/widget"; import mealie from "./mealie/widget"; import medusa from "./medusa/widget"; @@ -167,6 +168,7 @@ const widgets = { komga, kopia, lidarr, + linkwarden, mastodon, mealie, medusa,