diff --git a/.eslintrc.json b/.eslintrc.json
index fc0b1b8c..6c1da17d 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -2,6 +2,12 @@
"extends": ["airbnb", "next/core-web-vitals", "prettier"],
"plugins": ["prettier"],
"rules": {
+ "import/no-cycle": [
+ "error",
+ {
+ "maxDepth": 1
+ }
+ ],
"import/order": [
"error",
{
diff --git a/src/components/services/widget.jsx b/src/components/services/widget.jsx
index dd65b482..f6ae4204 100644
--- a/src/components/services/widget.jsx
+++ b/src/components/services/widget.jsx
@@ -1,72 +1,11 @@
-import dynamic from "next/dynamic";
import { useTranslation } from "next-i18next";
-const Sonarr = dynamic(() => import("./widgets/service/sonarr"));
-const Radarr = dynamic(() => import("./widgets/service/radarr"));
-const Lidarr = dynamic(() => import("./widgets/service/lidarr"));
-const Readarr = dynamic(() => import("./widgets/service/readarr"));
-const Bazarr = dynamic(() => import("./widgets/service/bazarr"));
-const Ombi = dynamic(() => import("./widgets/service/ombi"));
-const Portainer = dynamic(() => import("./widgets/service/portainer"));
-const Emby = dynamic(() => import("./widgets/service/emby"));
-const Nzbget = dynamic(() => import("./widgets/service/nzbget"));
-const SABnzbd = dynamic(() => import("./widgets/service/sabnzbd"));
-const Transmission = dynamic(() => import("./widgets/service/transmission"));
-const QBittorrent = dynamic(() => import("./widgets/service/qbittorrent"));
-const Docker = dynamic(() => import("./widgets/service/docker"));
-const Pihole = dynamic(() => import("./widgets/service/pihole"));
-const Rutorrent = dynamic(() => import("./widgets/service/rutorrent"));
-const Jellyfin = dynamic(() => import("./widgets/service/jellyfin"));
-const Speedtest = dynamic(() => import("./widgets/service/speedtest"));
-const Traefik = dynamic(() => import("./widgets/service/traefik"));
-const Jellyseerr = dynamic(() => import("./widgets/service/jellyseerr"));
-const Overseerr = dynamic(() => import("./widgets/service/overseerr"));
-const Npm = dynamic(() => import("./widgets/service/npm"));
-const Tautulli = dynamic(() => import("./widgets/service/tautulli"));
-const CoinMarketCap = dynamic(() => import("./widgets/service/coinmarketcap"));
-const Gotify = dynamic(() => import("./widgets/service/gotify"));
-const Prowlarr = dynamic(() => import("./widgets/service/prowlarr"));
-const Jackett = dynamic(() => import("./widgets/service/jackett"));
-const AdGuard = dynamic(() => import("./widgets/service/adguard"));
-const StRelaySrv = dynamic(() => import("./widgets/service/strelaysrv"));
-const Mastodon = dynamic(() => import("./widgets/service/mastodon"));
-
-const widgetMappings = {
- docker: Docker,
- sonarr: Sonarr,
- radarr: Radarr,
- lidarr: Lidarr,
- readarr: Readarr,
- bazarr: Bazarr,
- ombi: Ombi,
- portainer: Portainer,
- emby: Emby,
- jellyfin: Jellyfin,
- nzbget: Nzbget,
- sabnzbd: SABnzbd,
- transmission: Transmission,
- qbittorrent: QBittorrent,
- pihole: Pihole,
- rutorrent: Rutorrent,
- speedtest: Speedtest,
- traefik: Traefik,
- jellyseerr: Jellyseerr,
- overseerr: Overseerr,
- coinmarketcap: CoinMarketCap,
- npm: Npm,
- tautulli: Tautulli,
- gotify: Gotify,
- prowlarr: Prowlarr,
- jackett: Jackett,
- adguard: AdGuard,
- strelaysrv: StRelaySrv,
- mastodon: Mastodon,
-};
+import components from "widgets/components";
export default function Widget({ service }) {
const { t } = useTranslation("common");
- const ServiceWidget = widgetMappings[service.widget.type];
+ const ServiceWidget = components[service.widget.type];
if (ServiceWidget) {
return ;
diff --git a/src/components/services/widgets/service/adguard.jsx b/src/components/services/widgets/service/adguard.jsx
index a3ad75bb..1befec86 100644
--- a/src/components/services/widgets/service/adguard.jsx
+++ b/src/components/services/widgets/service/adguard.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function AdGuard({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: adguardData, error: adguardError } = useSWR(formatApiUrl(config, "stats"));
+ const { data: adguardData, error: adguardError } = useSWR(formatProxyUrl(config, "stats"));
if (adguardError) {
return ;
diff --git a/src/components/services/widgets/service/bazarr.jsx b/src/components/services/widgets/service/bazarr.jsx
index ef2c6cca..33b4defc 100644
--- a/src/components/services/widgets/service/bazarr.jsx
+++ b/src/components/services/widgets/service/bazarr.jsx
@@ -4,15 +4,15 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Bazarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: episodesData, error: episodesError } = useSWR(formatApiUrl(config, "episodes"));
- const { data: moviesData, error: moviesError } = useSWR(formatApiUrl(config, "movies"));
+ const { data: episodesData, error: episodesError } = useSWR(formatProxyUrl(config, "episodes"));
+ const { data: moviesData, error: moviesError } = useSWR(formatProxyUrl(config, "movies"));
if (episodesError || moviesError) {
return ;
diff --git a/src/components/services/widgets/service/coinmarketcap.jsx b/src/components/services/widgets/service/coinmarketcap.jsx
index 32b04713..d775e3fa 100644
--- a/src/components/services/widgets/service/coinmarketcap.jsx
+++ b/src/components/services/widgets/service/coinmarketcap.jsx
@@ -7,7 +7,7 @@ import Widget from "../widget";
import Block from "../block";
import Dropdown from "components/services/dropdown";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function CoinMarketCap({ service }) {
const { t } = useTranslation();
@@ -26,7 +26,7 @@ export default function CoinMarketCap({ service }) {
const { symbols } = config;
const { data: statsData, error: statsError } = useSWR(
- formatApiUrl(config, `v1/cryptocurrency/quotes/latest?symbol=${symbols.join(",")}&convert=${currencyCode}`)
+ formatProxyUrl(config, `v1/cryptocurrency/quotes/latest?symbol=${symbols.join(",")}&convert=${currencyCode}`)
);
if (!symbols || symbols.length === 0) {
diff --git a/src/components/services/widgets/service/emby.jsx b/src/components/services/widgets/service/emby.jsx
index 63ae9c8e..46ea129f 100644
--- a/src/components/services/widgets/service/emby.jsx
+++ b/src/components/services/widgets/service/emby.jsx
@@ -5,7 +5,7 @@ import { MdOutlineSmartDisplay } from "react-icons/md";
import Widget from "../widget";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
function ticksToTime(ticks) {
const milliseconds = ticks / 10000;
@@ -158,12 +158,12 @@ export default function Emby({ service }) {
data: sessionsData,
error: sessionsError,
mutate: sessionMutate,
- } = useSWR(formatApiUrl(config, "Sessions"), {
+ } = useSWR(formatProxyUrl(config, "Sessions"), {
refreshInterval: 5000,
});
async function handlePlayCommand(session, command) {
- const url = formatApiUrl(config, `Sessions/${session.Id}/Playing/${command}`);
+ const url = formatProxyUrl(config, `Sessions/${session.Id}/Playing/${command}`);
await fetch(url, {
method: "POST",
}).then(() => {
diff --git a/src/components/services/widgets/service/gotify.jsx b/src/components/services/widgets/service/gotify.jsx
index 4bd9f98b..29215574 100644
--- a/src/components/services/widgets/service/gotify.jsx
+++ b/src/components/services/widgets/service/gotify.jsx
@@ -4,16 +4,16 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Gotify({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: appsData, error: appsError } = useSWR(formatApiUrl(config, `application`));
- const { data: messagesData, error: messagesError } = useSWR(formatApiUrl(config, `message`));
- const { data: clientsData, error: clientsError } = useSWR(formatApiUrl(config, `client`));
+ const { data: appsData, error: appsError } = useSWR(formatProxyUrl(config, `application`));
+ const { data: messagesData, error: messagesError } = useSWR(formatProxyUrl(config, `message`));
+ const { data: clientsData, error: clientsError } = useSWR(formatProxyUrl(config, `client`));
if (appsError || messagesError || clientsError) {
return ;
diff --git a/src/components/services/widgets/service/jackett.jsx b/src/components/services/widgets/service/jackett.jsx
index 70de6c07..f9cc2dfb 100644
--- a/src/components/services/widgets/service/jackett.jsx
+++ b/src/components/services/widgets/service/jackett.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Jackett({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: indexersData, error: indexersError } = useSWR(formatApiUrl(config, "indexers"));
+ const { data: indexersData, error: indexersError } = useSWR(formatProxyUrl(config, "indexers"));
if (indexersError) {
return ;
diff --git a/src/components/services/widgets/service/jellyseerr.jsx b/src/components/services/widgets/service/jellyseerr.jsx
index 9bcc1e27..f4d5b71e 100644
--- a/src/components/services/widgets/service/jellyseerr.jsx
+++ b/src/components/services/widgets/service/jellyseerr.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Jellyseerr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `request/count`));
+ const { data: statsData, error: statsError } = useSWR(formatProxyUrl(config, `request/count`));
if (statsError) {
return ;
diff --git a/src/components/services/widgets/service/lidarr.jsx b/src/components/services/widgets/service/lidarr.jsx
index 2bf4cddb..4b985aee 100644
--- a/src/components/services/widgets/service/lidarr.jsx
+++ b/src/components/services/widgets/service/lidarr.jsx
@@ -4,16 +4,16 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Lidarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: albumsData, error: albumsError } = useSWR(formatApiUrl(config, "album"));
- const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
- const { data: queueData, error: queueError } = useSWR(formatApiUrl(config, "queue/status"));
+ const { data: albumsData, error: albumsError } = useSWR(formatProxyUrl(config, "album"));
+ const { data: wantedData, error: wantedError } = useSWR(formatProxyUrl(config, "wanted/missing"));
+ const { data: queueData, error: queueError } = useSWR(formatProxyUrl(config, "queue/status"));
if (albumsError || wantedError || queueError) {
return ;
diff --git a/src/components/services/widgets/service/mastodon.jsx b/src/components/services/widgets/service/mastodon.jsx
index 20b14aca..d1bb2252 100644
--- a/src/components/services/widgets/service/mastodon.jsx
+++ b/src/components/services/widgets/service/mastodon.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Mastodon({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `instance`));
+ const { data: statsData, error: statsError } = useSWR(formatProxyUrl(config, `instance`));
if (statsError) {
return ;
@@ -29,7 +29,7 @@ export default function Mastodon({ service }) {
return (
-
+
diff --git a/src/components/services/widgets/service/npm.jsx b/src/components/services/widgets/service/npm.jsx
index 563348c2..93ecf26b 100644
--- a/src/components/services/widgets/service/npm.jsx
+++ b/src/components/services/widgets/service/npm.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Npm({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: infoData, error: infoError } = useSWR(formatApiUrl(config, "nginx/proxy-hosts"));
+ const { data: infoData, error: infoError } = useSWR(formatProxyUrl(config, "nginx/proxy-hosts"));
if (infoError) {
return ;
diff --git a/src/components/services/widgets/service/nzbget.jsx b/src/components/services/widgets/service/nzbget.jsx
index b8125843..58d08850 100644
--- a/src/components/services/widgets/service/nzbget.jsx
+++ b/src/components/services/widgets/service/nzbget.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Nzbget({ service }) {
const { t } = useTranslation("common");
const config = service.widget;
- const { data: statusData, error: statusError } = useSWR(formatApiUrl(config, "status"));
+ const { data: statusData, error: statusError } = useSWR(formatProxyUrl(config, "status"));
if (statusError) {
return ;
diff --git a/src/components/services/widgets/service/ombi.jsx b/src/components/services/widgets/service/ombi.jsx
index aa1a5f65..887c7348 100644
--- a/src/components/services/widgets/service/ombi.jsx
+++ b/src/components/services/widgets/service/ombi.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Ombi({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `Request/count`));
+ const { data: statsData, error: statsError } = useSWR(formatProxyUrl(config, `Request/count`));
if (statsError) {
return ;
diff --git a/src/components/services/widgets/service/overseerr.jsx b/src/components/services/widgets/service/overseerr.jsx
index 59644e0a..83417236 100644
--- a/src/components/services/widgets/service/overseerr.jsx
+++ b/src/components/services/widgets/service/overseerr.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Overseerr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `request/count`));
+ const { data: statsData, error: statsError } = useSWR(formatProxyUrl(config, `request/count`));
if (statsError) {
return ;
diff --git a/src/components/services/widgets/service/pihole.jsx b/src/components/services/widgets/service/pihole.jsx
index 3468f5fc..720cd681 100644
--- a/src/components/services/widgets/service/pihole.jsx
+++ b/src/components/services/widgets/service/pihole.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Pihole({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: piholeData, error: piholeError } = useSWR(formatApiUrl(config, "api.php"));
+ const { data: piholeData, error: piholeError } = useSWR(formatProxyUrl(config, "api.php"));
if (piholeError) {
return ;
diff --git a/src/components/services/widgets/service/portainer.jsx b/src/components/services/widgets/service/portainer.jsx
index ab1c6895..1c28333b 100644
--- a/src/components/services/widgets/service/portainer.jsx
+++ b/src/components/services/widgets/service/portainer.jsx
@@ -4,14 +4,16 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Portainer({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: containersData, error: containersError } = useSWR(formatApiUrl(config, `docker/containers/json?all=1`));
+ const { data: containersData, error: containersError } = useSWR(
+ formatProxyUrl(config, `docker/containers/json?all=1`)
+ );
if (containersError) {
return ;
diff --git a/src/components/services/widgets/service/prowlarr.jsx b/src/components/services/widgets/service/prowlarr.jsx
index b46eb7a3..e80b9e3d 100644
--- a/src/components/services/widgets/service/prowlarr.jsx
+++ b/src/components/services/widgets/service/prowlarr.jsx
@@ -4,16 +4,16 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Prowlarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: indexersData, error: indexersError } = useSWR(formatApiUrl(config, "indexer"));
- const { data: grabsData, error: grabsError } = useSWR(formatApiUrl(config, "indexerstats"));
-
+ const { data: indexersData, error: indexersError } = useSWR(formatProxyUrl(config, "indexer"));
+ const { data: grabsData, error: grabsError } = useSWR(formatProxyUrl(config, "indexerstats"));
+
if (indexersError || grabsError) {
return ;
}
@@ -32,11 +32,11 @@ export default function Prowlarr({ service }) {
const indexers = indexersData?.filter((indexer) => indexer.enable === true);
- let numberOfGrabs = 0
- let numberOfQueries = 0
- let numberOfFailedGrabs = 0
- let numberOfFailedQueries = 0
- grabsData?.indexers?.forEach(element => {
+ let numberOfGrabs = 0;
+ let numberOfQueries = 0;
+ let numberOfFailedGrabs = 0;
+ let numberOfFailedQueries = 0;
+ grabsData?.indexers?.forEach((element) => {
numberOfGrabs += element.numberOfGrabs;
numberOfQueries += element.numberOfQueries;
numberOfFailedGrabs += numberOfFailedGrabs + element.numberOfFailedGrabs;
diff --git a/src/components/services/widgets/service/qbittorrent.jsx b/src/components/services/widgets/service/qbittorrent.jsx
index 954b9b05..e7030cd8 100644
--- a/src/components/services/widgets/service/qbittorrent.jsx
+++ b/src/components/services/widgets/service/qbittorrent.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
-export default function QBittorrent ({ service }) {
+export default function QBittorrent({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: torrentData, error: torrentError } = useSWR(formatApiUrl(config, "torrents/info"));
+ const { data: torrentData, error: torrentError } = useSWR(formatProxyUrl(config, "torrents/info"));
if (torrentError) {
return ;
diff --git a/src/components/services/widgets/service/radarr.jsx b/src/components/services/widgets/service/radarr.jsx
index 5fc44ff0..f738ab71 100644
--- a/src/components/services/widgets/service/radarr.jsx
+++ b/src/components/services/widgets/service/radarr.jsx
@@ -4,14 +4,15 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Radarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: moviesData, error: moviesError } = useSWR(formatApiUrl(config, "movie"));
- const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue/status"));
+ const { data: moviesData, error: moviesError } = useSWR(formatProxyUrl(config, "movie"));
+ const { data: queuedData, error: queuedError } = useSWR(formatProxyUrl(config, "queue/status"));
if (moviesError || queuedError) {
return ;
diff --git a/src/components/services/widgets/service/readarr.jsx b/src/components/services/widgets/service/readarr.jsx
index 2f478768..aab6290a 100644
--- a/src/components/services/widgets/service/readarr.jsx
+++ b/src/components/services/widgets/service/readarr.jsx
@@ -4,16 +4,16 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Readarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: booksData, error: booksError } = useSWR(formatApiUrl(config, "book"));
- const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
- const { data: queueData, error: queueError } = useSWR(formatApiUrl(config, "queue/status"));
+ const { data: booksData, error: booksError } = useSWR(formatProxyUrl(config, "book"));
+ const { data: wantedData, error: wantedError } = useSWR(formatProxyUrl(config, "wanted/missing"));
+ const { data: queueData, error: queueError } = useSWR(formatProxyUrl(config, "queue/status"));
if (booksError || wantedError || queueError) {
return ;
diff --git a/src/components/services/widgets/service/rutorrent.jsx b/src/components/services/widgets/service/rutorrent.jsx
index ecbe24b5..6aba5e67 100644
--- a/src/components/services/widgets/service/rutorrent.jsx
+++ b/src/components/services/widgets/service/rutorrent.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Rutorrent({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: statusData, error: statusError } = useSWR(formatApiUrl(config));
+ const { data: statusData, error: statusError } = useSWR(formatProxyUrl(config));
if (statusError) {
return ;
diff --git a/src/components/services/widgets/service/sabnzbd.jsx b/src/components/services/widgets/service/sabnzbd.jsx
index 14caa4dc..a79e11fe 100644
--- a/src/components/services/widgets/service/sabnzbd.jsx
+++ b/src/components/services/widgets/service/sabnzbd.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function SABnzbd({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: queueData, error: queueError } = useSWR(formatApiUrl(config, "queue"));
+ const { data: queueData, error: queueError } = useSWR(formatProxyUrl(config, "queue"));
if (queueError) {
return ;
diff --git a/src/components/services/widgets/service/sonarr.jsx b/src/components/services/widgets/service/sonarr.jsx
index 584a3d78..ea91388b 100644
--- a/src/components/services/widgets/service/sonarr.jsx
+++ b/src/components/services/widgets/service/sonarr.jsx
@@ -4,16 +4,16 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Sonarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
- const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue"));
- const { data: seriesData, error: seriesError } = useSWR(formatApiUrl(config, "series"));
+ const { data: wantedData, error: wantedError } = useSWR(formatProxyUrl(config, "wanted/missing"));
+ const { data: queuedData, error: queuedError } = useSWR(formatProxyUrl(config, "queue"));
+ const { data: seriesData, error: seriesError } = useSWR(formatProxyUrl(config, "series"));
if (wantedError || queuedError || seriesError) {
return ;
diff --git a/src/components/services/widgets/service/speedtest.jsx b/src/components/services/widgets/service/speedtest.jsx
index a4416b83..def1336e 100644
--- a/src/components/services/widgets/service/speedtest.jsx
+++ b/src/components/services/widgets/service/speedtest.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Speedtest({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: speedtestData, error: speedtestError } = useSWR(formatApiUrl(config, "speedtest/latest"));
+ const { data: speedtestData, error: speedtestError } = useSWR(formatProxyUrl(config, "speedtest/latest"));
if (speedtestError) {
return ;
diff --git a/src/components/services/widgets/service/strelaysrv.jsx b/src/components/services/widgets/service/strelaysrv.jsx
index 5533d89a..a1aff994 100644
--- a/src/components/services/widgets/service/strelaysrv.jsx
+++ b/src/components/services/widgets/service/strelaysrv.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function StRelaySrv({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `status`));
+ const { data: statsData, error: statsError } = useSWR(formatProxyUrl(config, `status`));
if (statsError) {
return ;
@@ -29,10 +29,16 @@ export default function StRelaySrv({ service }) {
return (
-
-
-
-
+
+
+
+
);
}
diff --git a/src/components/services/widgets/service/tautulli.jsx b/src/components/services/widgets/service/tautulli.jsx
index 54fc654b..9ba4306f 100644
--- a/src/components/services/widgets/service/tautulli.jsx
+++ b/src/components/services/widgets/service/tautulli.jsx
@@ -6,7 +6,7 @@ import { MdOutlineSmartDisplay, MdSmartDisplay } from "react-icons/md";
import Widget from "../widget";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
function millisecondsToTime(milliseconds) {
const seconds = Math.floor((milliseconds / 1000) % 60);
@@ -120,7 +120,7 @@ export default function Tautulli({ service }) {
const config = service.widget;
- const { data: activityData, error: activityError } = useSWR(formatApiUrl(config, "get_activity"), {
+ const { data: activityData, error: activityError } = useSWR(formatProxyUrl(config, "get_activity"), {
refreshInterval: 5000,
});
diff --git a/src/components/services/widgets/service/traefik.jsx b/src/components/services/widgets/service/traefik.jsx
index 816a40a6..efef287e 100644
--- a/src/components/services/widgets/service/traefik.jsx
+++ b/src/components/services/widgets/service/traefik.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Traefik({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: traefikData, error: traefikError } = useSWR(formatApiUrl(config, "overview"));
+ const { data: traefikData, error: traefikError } = useSWR(formatProxyUrl(config, "overview"));
if (traefikError) {
return ;
diff --git a/src/components/services/widgets/service/transmission.jsx b/src/components/services/widgets/service/transmission.jsx
index bb3eeea6..fb449e28 100644
--- a/src/components/services/widgets/service/transmission.jsx
+++ b/src/components/services/widgets/service/transmission.jsx
@@ -4,14 +4,14 @@ import { useTranslation } from "next-i18next";
import Widget from "../widget";
import Block from "../block";
-import { formatApiUrl } from "utils/api-helpers";
+import { formatProxyUrl } from "utils/api-helpers";
export default function Transmission({ service }) {
const { t } = useTranslation();
const config = service.widget;
- const { data: torrentData, error: torrentError } = useSWR(formatApiUrl(config));
+ const { data: torrentData, error: torrentError } = useSWR(formatProxyUrl(config));
if (torrentError) {
return ;
@@ -37,7 +37,7 @@ export default function Transmission({ service }) {
const torrent = torrents[i];
rateDl += torrent.rateDownload;
rateUl += torrent.rateUpload;
- if (torrent.percentDone === 1) {
+ if (torrent.percentDone === 1) {
completed += 1;
}
}
diff --git a/src/pages/api/services/proxy.js b/src/pages/api/services/proxy.js
index 5b1cbd93..b73ec40b 100644
--- a/src/pages/api/services/proxy.js
+++ b/src/pages/api/services/proxy.js
@@ -1,128 +1,45 @@
import createLogger from "utils/logger";
import genericProxyHandler from "utils/proxies/generic";
-import credentialedProxyHandler from "utils/proxies/credentialed";
-import rutorrentProxyHandler from "utils/proxies/rutorrent";
-import nzbgetProxyHandler from "utils/proxies/nzbget";
-import npmProxyHandler from "utils/proxies/npm";
-import transmissionProxyHandler from "utils/proxies/transmission";
-import qbittorrentProxyHandler from "utils/proxies/qbittorrent";
+import widgets from "widgets/widgets";
-const logger = createLogger('servicesProxy');
-
-function asJson(data) {
- if (data?.length > 0) {
- const json = JSON.parse(data.toString());
- return json;
- }
- return data;
-}
-
-function jsonArrayTransform(data, transform) {
- const json = asJson(data);
- if (json instanceof Array) {
- return transform(json);
- }
- return json;
-}
-
-function jsonArrayFilter(data, filter) {
- return jsonArrayTransform(data, (items) => items.filter(filter));
-}
-
-const serviceProxyHandlers = {
- // uses query param auth
- emby: genericProxyHandler,
- jellyfin: genericProxyHandler,
- pihole: genericProxyHandler,
- radarr: {
- proxy: genericProxyHandler,
- maps: {
- movie: (data) => ({
- wanted: jsonArrayFilter(data, (item) => item.isAvailable === false).length,
- have: jsonArrayFilter(data, (item) => item.isAvailable === true).length,
- }),
- },
- },
- sonarr: {
- proxy: genericProxyHandler,
- maps: {
- series: (data) => ({
- total: asJson(data).length,
- }),
- },
- },
- lidarr: {
- proxy: genericProxyHandler,
- maps: {
- album: (data) => ({
- have: jsonArrayFilter(data, (item) => item?.statistics?.percentOfTracks === 100).length,
- }),
- },
- },
- readarr: {
- proxy: genericProxyHandler,
- maps: {
- book: (data) => ({
- have: jsonArrayFilter(data, (item) => item?.statistics?.bookFileCount > 0).length,
- }),
- },
- },
- bazarr: {
- proxy: genericProxyHandler,
- maps: {
- movies: (data) => ({
- total: asJson(data).total,
- }),
- episodes: (data) => ({
- total: asJson(data).total,
- }),
- },
- },
- speedtest: genericProxyHandler,
- tautulli: genericProxyHandler,
- traefik: genericProxyHandler,
- sabnzbd: genericProxyHandler,
- jackett: genericProxyHandler,
- adguard: genericProxyHandler,
- strelaysrv: genericProxyHandler,
- mastodon: genericProxyHandler,
- // uses X-API-Key (or similar) header auth
- gotify: credentialedProxyHandler,
- portainer: credentialedProxyHandler,
- jellyseerr: credentialedProxyHandler,
- overseerr: credentialedProxyHandler,
- ombi: credentialedProxyHandler,
- coinmarketcap: credentialedProxyHandler,
- prowlarr: credentialedProxyHandler,
- // super specific handlers
- rutorrent: rutorrentProxyHandler,
- nzbget: nzbgetProxyHandler,
- npm: npmProxyHandler,
- transmission: transmissionProxyHandler,
- qbittorrent: qbittorrentProxyHandler,
-};
+const logger = createLogger("servicesProxy");
export default async function handler(req, res) {
try {
const { type } = req.query;
+ const widget = widgets[type];
- const serviceProxyHandler = serviceProxyHandlers[type];
+ if (!widget) {
+ logger.debug("Unknown proxy service type: %s", type);
+ return res.status(403).json({ error: "Unkown proxy service type" });
+ }
- if (serviceProxyHandler) {
- if (serviceProxyHandler instanceof Function) {
- return serviceProxyHandler(req, res);
+ const serviceProxyHandler = widget.proxyHandler || genericProxyHandler;
+
+ if (serviceProxyHandler instanceof Function) {
+ // map opaque endpoints to their actual endpoint
+ const mapping = widget?.mappings?.[req.query.endpoint];
+ const map = mapping?.map;
+ const endpoint = mapping?.endpoint;
+ const endpointProxy = mapping?.proxyHandler;
+
+ if (!endpoint) {
+ logger.debug("Unsupported service endpoint: %s", type);
+ return res.status(403).json({ error: "Unsupported service endpoint" });
}
-
- const { proxy, maps } = serviceProxyHandler;
- if (proxy) {
- return proxy(req, res, maps);
+
+ req.query.endpoint = endpoint;
+
+ if (endpointProxy instanceof Function) {
+ return endpointProxy(req, res, map);
}
+
+ return serviceProxyHandler(req, res, map);
}
logger.debug("Unknown proxy service type: %s", type);
return res.status(403).json({ error: "Unkown proxy service type" });
- }
- catch (ex) {
+ } catch (ex) {
logger.error(ex);
return res.status(500).send({ error: "Unexpected error" });
}
diff --git a/src/utils/api-helpers.js b/src/utils/api-helpers.js
index 4b5b72d1..c0a2314a 100644
--- a/src/utils/api-helpers.js
+++ b/src/utils/api-helpers.js
@@ -1,44 +1,44 @@
-const formats = {
- emby: `{url}/emby/{endpoint}?api_key={key}`,
- jellyfin: `{url}/emby/{endpoint}?api_key={key}`,
- pihole: `{url}/admin/{endpoint}`,
- radarr: `{url}/api/v3/{endpoint}?apikey={key}`,
- sonarr: `{url}/api/v3/{endpoint}?apikey={key}`,
- speedtest: `{url}/api/{endpoint}`,
- tautulli: `{url}/api/v2?apikey={key}&cmd={endpoint}`,
- traefik: `{url}/api/{endpoint}`,
- portainer: `{url}/api/endpoints/{env}/{endpoint}`,
- rutorrent: `{url}/plugins/httprpc/action.php`,
- transmission: `{url}/transmission/rpc`,
- qbittorrent: `{url}/api/v2/{endpoint}`,
- jellyseerr: `{url}/api/v1/{endpoint}`,
- overseerr: `{url}/api/v1/{endpoint}`,
- ombi: `{url}/api/v1/{endpoint}`,
- npm: `{url}/api/{endpoint}`,
- lidarr: `{url}/api/v1/{endpoint}?apikey={key}`,
- readarr: `{url}/api/v1/{endpoint}?apikey={key}`,
- bazarr: `{url}/api/{endpoint}/wanted?apikey={key}`,
- sabnzbd: `{url}/api/?apikey={key}&output=json&mode={endpoint}`,
- coinmarketcap: `https://pro-api.coinmarketcap.com/{endpoint}`,
- gotify: `{url}/{endpoint}`,
- prowlarr: `{url}/api/v1/{endpoint}`,
- jackett: `{url}/api/v2.0/{endpoint}?apikey={key}&configured=true`,
- adguard: `{url}/control/{endpoint}`,
- strelaysrv: `{url}/{endpoint}`,
- mastodon: `{url}/api/v1/{endpoint}`,
-};
+// const formats = {
+// emby: `{url}/emby/{endpoint}?api_key={key}`,
+// jellyfin: `{url}/emby/{endpoint}?api_key={key}`,
+// pihole: `{url}/admin/{endpoint}`,
+// radarr: `{url}/api/v3/{endpoint}?apikey={key}`,
+// sonarr: `{url}/api/v3/{endpoint}?apikey={key}`,
+// speedtest: `{url}/api/{endpoint}`,
+// tautulli: `{url}/api/v2?apikey={key}&cmd={endpoint}`,
+// traefik: `{url}/api/{endpoint}`,
+// portainer: `{url}/api/endpoints/{env}/{endpoint}`,
+// rutorrent: `{url}/plugins/httprpc/action.php`,
+// transmission: `{url}/transmission/rpc`,
+// qbittorrent: `{url}/api/v2/{endpoint}`,
+// jellyseerr: `{url}/api/v1/{endpoint}`,
+// overseerr: `{url}/api/v1/{endpoint}`,
+// ombi: `{url}/api/v1/{endpoint}`,
+// npm: `{url}/api/{endpoint}`,
+// lidarr: `{url}/api/v1/{endpoint}?apikey={key}`,
+// readarr: `{url}/api/v1/{endpoint}?apikey={key}`,
+// bazarr: `{url}/api/{endpoint}/wanted?apikey={key}`,
+// sabnzbd: `{url}/api/?apikey={key}&output=json&mode={endpoint}`,
+// coinmarketcap: `https://pro-api.coinmarketcap.com/{endpoint}`,
+// gotify: `{url}/{endpoint}`,
+// prowlarr: `{url}/api/v1/{endpoint}`,
+// jackett: `{url}/api/v2.0/{endpoint}?apikey={key}&configured=true`,
+// adguard: `{url}/control/{endpoint}`,
+// strelaysrv: `{url}/{endpoint}`,
+// mastodon: `{url}/api/v1/{endpoint}`,
+// };
-export function formatApiCall(api, args) {
+export function formatApiCall(url, args) {
const find = /\{.*?\}/g;
const replace = (match) => {
const key = match.replace(/\{|\}/g, "");
return args[key];
};
- return formats[api].replace(find, replace);
+ return url.replace(find, replace);
}
-export function formatApiUrl(widget, endpoint) {
+export function formatProxyUrl(widget, endpoint) {
const params = new URLSearchParams({
type: widget.type,
group: widget.service_group,
@@ -47,3 +47,23 @@ export function formatApiUrl(widget, endpoint) {
});
return `/api/services/proxy?${params.toString()}`;
}
+
+export function asJson(data) {
+ if (data?.length > 0) {
+ const json = JSON.parse(data.toString());
+ return json;
+ }
+ return data;
+}
+
+export function jsonArrayTransform(data, transform) {
+ const json = asJson(data);
+ if (json instanceof Array) {
+ return transform(json);
+ }
+ return json;
+}
+
+export function jsonArrayFilter(data, filter) {
+ return jsonArrayTransform(data, (items) => items.filter(filter));
+}
diff --git a/src/utils/proxies/credentialed.js b/src/utils/proxies/credentialed.js
index 736da48e..016770a6 100644
--- a/src/utils/proxies/credentialed.js
+++ b/src/utils/proxies/credentialed.js
@@ -1,6 +1,7 @@
import getServiceWidget from "utils/service-helpers";
import { formatApiCall } from "utils/api-helpers";
import { httpProxy } from "utils/http";
+import widgets from "widgets/widgets";
export default async function credentialedProxyHandler(req, res) {
const { group, service, endpoint } = req.query;
@@ -8,8 +9,12 @@ export default async function credentialedProxyHandler(req, res) {
if (group && service) {
const widget = await getServiceWidget(group, service);
+ if (!widgets?.[widget.type]?.api) {
+ return res.status(403).json({ error: "Service does not support API calls" });
+ }
+
if (widget) {
- const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
+ const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
const headers = {
"Content-Type": "application/json",
diff --git a/src/utils/proxies/generic.js b/src/utils/proxies/generic.js
index 70dab4aa..0f911aeb 100644
--- a/src/utils/proxies/generic.js
+++ b/src/utils/proxies/generic.js
@@ -2,17 +2,22 @@ import getServiceWidget from "utils/service-helpers";
import { formatApiCall } from "utils/api-helpers";
import { httpProxy } from "utils/http";
import createLogger from "utils/logger";
+import widgets from "widgets/widgets";
-const logger = createLogger('genericProxyHandler');
+const logger = createLogger("genericProxyHandler");
-export default async function genericProxyHandler(req, res, maps) {
+export default async function genericProxyHandler(req, res, map) {
const { group, service, endpoint } = req.query;
if (group && service) {
const widget = await getServiceWidget(group, service);
+ if (!widgets?.[widget.type]?.api) {
+ return res.status(403).json({ error: "Service does not support API calls" });
+ }
+
if (widget) {
- const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
+ const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
let headers;
if (widget.username && widget.password) {
@@ -27,8 +32,8 @@ export default async function genericProxyHandler(req, res, maps) {
});
let resultData = data;
- if ((status === 200) && (maps?.[endpoint])) {
- resultData = maps[endpoint](data);
+ if (status === 200 && map) {
+ resultData = map(data);
}
if (contentType) res.setHeader("Content-Type", contentType);
diff --git a/src/utils/proxies/npm.js b/src/utils/proxies/npm.js
index d60cedd1..bd612a50 100644
--- a/src/utils/proxies/npm.js
+++ b/src/utils/proxies/npm.js
@@ -1,5 +1,6 @@
import getServiceWidget from "utils/service-helpers";
import { formatApiCall } from "utils/api-helpers";
+import widgets from "widgets/widgets";
export default async function npmProxyHandler(req, res) {
const { group, service, endpoint } = req.query;
@@ -7,8 +8,12 @@ export default async function npmProxyHandler(req, res) {
if (group && service) {
const widget = await getServiceWidget(group, service);
+ if (!widgets?.[widget.type]?.api) {
+ return res.status(403).json({ error: "Service does not support API calls" });
+ }
+
if (widget) {
- const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
+ const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
const loginUrl = `${widget.url}/api/tokens`;
const body = { identity: widget.username, secret: widget.password };
diff --git a/src/utils/proxies/qbittorrent.js b/src/utils/proxies/qbittorrent.js
index df341000..dfb46a25 100644
--- a/src/utils/proxies/qbittorrent.js
+++ b/src/utils/proxies/qbittorrent.js
@@ -12,15 +12,15 @@ async function login(widget, params) {
return fetch(loginUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
- body: loginBody
+ body: loginBody,
})
- .then(async response => {
- addCookieToJar(loginUrl, response.headers);
- setCookieHeader(loginUrl, params);
- const data = await response.text();
- return ([response.status, data]);
- })
- .catch(err => ([500, err]));
+ .then(async (response) => {
+ addCookieToJar(loginUrl, response.headers);
+ setCookieHeader(loginUrl, params);
+ const data = await response.text();
+ return [response.status, data];
+ })
+ .catch((err) => [500, err]);
}
export default async function qbittorrentProxyHandler(req, res) {
@@ -46,7 +46,7 @@ export default async function qbittorrentProxyHandler(req, res) {
if (status !== 200) {
return res.status(status).end(data);
}
- if (data.toString() !== 'Ok.') {
+ if (data.toString() !== "Ok.") {
return res.status(401).end(data);
}
}
@@ -55,4 +55,4 @@ export default async function qbittorrentProxyHandler(req, res) {
if (contentType) res.setHeader("Content-Type", contentType);
return res.status(status).send(data);
-}
\ No newline at end of file
+}
diff --git a/src/widgets/components.js b/src/widgets/components.js
new file mode 100644
index 00000000..086da75a
--- /dev/null
+++ b/src/widgets/components.js
@@ -0,0 +1,8 @@
+import dynamic from "next/dynamic";
+
+const components = {
+ overseerr: dynamic(() => import("./overseerr/component")),
+ radarr: dynamic(() => import("./radarr/component")),
+};
+
+export default components;
diff --git a/src/widgets/overseerr/component.jsx b/src/widgets/overseerr/component.jsx
new file mode 100644
index 00000000..0201ca81
--- /dev/null
+++ b/src/widgets/overseerr/component.jsx
@@ -0,0 +1,36 @@
+import useSWR from "swr";
+import { useTranslation } from "next-i18next";
+
+import Widget from "components/services/widgets/widget";
+import Block from "components/services/widgets/block";
+import { formatProxyUrl } from "utils/api-helpers";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const config = service.widget;
+
+ const { data: statsData, error: statsError } = useSWR(formatProxyUrl(config, "request/count"));
+
+ if (statsError) {
+ return ;
+ }
+
+ if (!statsData) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/overseerr/widget.js b/src/widgets/overseerr/widget.js
new file mode 100644
index 00000000..b46bad90
--- /dev/null
+++ b/src/widgets/overseerr/widget.js
@@ -0,0 +1,14 @@
+import credentialedProxyHandler from "utils/proxies/credentialed";
+
+const widget = {
+ api: "{url}/api/v1/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ "request/count": {
+ endpoint: "request/count",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/radarr/component.jsx b/src/widgets/radarr/component.jsx
new file mode 100644
index 00000000..94b85acd
--- /dev/null
+++ b/src/widgets/radarr/component.jsx
@@ -0,0 +1,37 @@
+import useSWR from "swr";
+import { useTranslation } from "next-i18next";
+
+import Widget from "components/services/widgets/widget";
+import Block from "components/services/widgets/block";
+import { formatProxyUrl } from "utils/api-helpers";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const config = service.widget;
+
+ const { data: moviesData, error: moviesError } = useSWR(formatProxyUrl(config, "movie"));
+ const { data: queuedData, error: queuedError } = useSWR(formatProxyUrl(config, "queue/status"));
+
+ if (moviesError || queuedError) {
+ return ;
+ }
+
+ if (!moviesData || !queuedData) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/radarr/widget.js b/src/widgets/radarr/widget.js
new file mode 100644
index 00000000..8e832559
--- /dev/null
+++ b/src/widgets/radarr/widget.js
@@ -0,0 +1,22 @@
+import genericProxyHandler from "utils/proxies/generic";
+import { jsonArrayFilter } from "utils/api-helpers";
+
+const widget = {
+ api: "{url}/api/v3/{endpoint}?apikey={key}",
+ proxyHandler: genericProxyHandler,
+
+ mappings: {
+ movie: {
+ endpoint: "movie",
+ map: (data) => ({
+ wanted: jsonArrayFilter(data, (item) => item.isAvailable === false).length,
+ have: jsonArrayFilter(data, (item) => item.isAvailable === true).length,
+ }),
+ },
+ "queue/status": {
+ endpoint: "queue/status",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
new file mode 100644
index 00000000..db4f22ef
--- /dev/null
+++ b/src/widgets/widgets.js
@@ -0,0 +1,9 @@
+import overseerr from "./overseerr/widget";
+import radarr from "./radarr/widget";
+
+const widgets = {
+ overseerr,
+ radarr,
+};
+
+export default widgets;
diff --git a/tailwind.config.js b/tailwind.config.js
index d9b2ea0e..b6e27a30 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -4,7 +4,11 @@ const tailwindScrollbars = require("tailwind-scrollbar");
module.exports = {
darkMode: "class",
- content: ["./src/pages/**/*.{js,ts,jsx,tsx}", "./src/components/**/*.{js,ts,jsx,tsx}"],
+ content: [
+ "./src/pages/**/*.{js,ts,jsx,tsx}",
+ "./src/components/**/*.{js,ts,jsx,tsx}",
+ "./src/widgets/**/*.{js,ts,jsx,tsx}",
+ ],
theme: {
extend: {
colors: {