diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx
index 93fa83ac..7974789f 100644
--- a/src/components/services/item.jsx
+++ b/src/components/services/item.jsx
@@ -4,8 +4,8 @@ import { useContext, useState } from "react";
import Status from "./status";
import Widget from "./widget";
-import Docker from "./widgets/service/docker";
+import Docker from "widgets/docker/component";
import { SettingsContext } from "utils/settings-context";
function resolveIcon(icon) {
diff --git a/src/components/services/widgets/service/adguard.jsx b/src/components/services/widgets/service/adguard.jsx
deleted file mode 100644
index 1befec86..00000000
--- a/src/components/services/widgets/service/adguard.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function AdGuard({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: adguardData, error: adguardError } = useSWR(formatProxyUrl(config, "stats"));
-
- if (adguardError) {
- return ;
- }
-
- if (!adguardData) {
- return (
-
-
-
-
-
-
- );
- }
-
- const filtered =
- adguardData.num_replaced_safebrowsing + adguardData.num_replaced_safesearch + adguardData.num_replaced_parental;
-
- return (
-
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/bazarr.jsx b/src/components/services/widgets/service/bazarr.jsx
deleted file mode 100644
index 33b4defc..00000000
--- a/src/components/services/widgets/service/bazarr.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Bazarr({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: episodesData, error: episodesError } = useSWR(formatProxyUrl(config, "episodes"));
- const { data: moviesData, error: moviesError } = useSWR(formatProxyUrl(config, "movies"));
-
- if (episodesError || moviesError) {
- return ;
- }
-
- if (!episodesData || !moviesData) {
- return (
-
-
-
-
- );
- }
-
- return (
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/coinmarketcap.jsx b/src/components/services/widgets/service/coinmarketcap.jsx
deleted file mode 100644
index d775e3fa..00000000
--- a/src/components/services/widgets/service/coinmarketcap.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import useSWR from "swr";
-import { useState } from "react";
-import { useTranslation } from "next-i18next";
-import classNames from "classnames";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import Dropdown from "components/services/dropdown";
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function CoinMarketCap({ service }) {
- const { t } = useTranslation();
-
- const dateRangeOptions = [
- { label: t("coinmarketcap.1hour"), value: "1h" },
- { label: t("coinmarketcap.1day"), value: "24h" },
- { label: t("coinmarketcap.7days"), value: "7d" },
- { label: t("coinmarketcap.30days"), value: "30d" },
- ];
-
- const [dateRange, setDateRange] = useState(dateRangeOptions[0].value);
-
- const config = service.widget;
- const currencyCode = config.currency ?? "USD";
- const { symbols } = config;
-
- const { data: statsData, error: statsError } = useSWR(
- formatProxyUrl(config, `v1/cryptocurrency/quotes/latest?symbol=${symbols.join(",")}&convert=${currencyCode}`)
- );
-
- if (!symbols || symbols.length === 0) {
- return (
-
-
-
- );
- }
-
- if (statsError) {
- return ;
- }
-
- if (!statsData || !dateRange) {
- return (
-
-
-
- );
- }
-
- const { data } = statsData;
-
- return (
-
-
-
-
-
-
- {symbols.map((symbol) => (
-
-
{data[symbol].name}
-
-
- {t("common.number", {
- value: data[symbol].quote[currencyCode].price,
- style: "currency",
- currency: currencyCode,
- })}
-
-
0
- ? "text-emerald-300"
- : "text-rose-300"
- }`}
- >
- {data[symbol].quote[currencyCode][`percent_change_${dateRange}`].toFixed(2)}%
-
-
-
- ))}
-
-
- );
-}
diff --git a/src/components/services/widgets/service/docker.jsx b/src/components/services/widgets/service/docker.jsx
deleted file mode 100644
index ca9476db..00000000
--- a/src/components/services/widgets/service/docker.jsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import calculateCPUPercent from "widgets/docker/stats-helpers";
-
-export default function Docker({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: statusData, error: statusError } = useSWR(
- `/api/docker/status/${config.container}/${config.server || ""}`,
- {
- refreshInterval: 5000,
- }
- );
-
- const { data: statsData, error: statsError } = useSWR(
- `/api/docker/stats/${config.container}/${config.server || ""}`,
- {
- refreshInterval: 5000,
- }
- );
-
- if (statsError || statusError) {
- return ;
- }
-
- if (statusData && statusData.status !== "running") {
- return (
-
-
-
- );
- }
-
- if (!statsData || !statusData) {
- return (
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
- {statsData.stats.networks && (
- <>
-
-
- >
- )}
-
- );
-}
diff --git a/src/components/services/widgets/service/emby.jsx b/src/components/services/widgets/service/emby.jsx
deleted file mode 100644
index 46ea129f..00000000
--- a/src/components/services/widgets/service/emby.jsx
+++ /dev/null
@@ -1,239 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-import { BsVolumeMuteFill, BsFillPlayFill, BsPauseFill, BsCpu, BsFillCpuFill } from "react-icons/bs";
-import { MdOutlineSmartDisplay } from "react-icons/md";
-
-import Widget from "../widget";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-function ticksToTime(ticks) {
- const milliseconds = ticks / 10000;
- const seconds = Math.floor((milliseconds / 1000) % 60);
- const minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
- const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
- return { hours, minutes, seconds };
-}
-
-function ticksToString(ticks) {
- const { hours, minutes, seconds } = ticksToTime(ticks);
- const parts = [];
- if (hours > 0) {
- parts.push(hours);
- }
- parts.push(minutes);
- parts.push(seconds);
-
- return parts.map((part) => part.toString().padStart(2, "0")).join(":");
-}
-
-function SingleSessionEntry({ playCommand, session }) {
- const {
- NowPlayingItem: { Name, SeriesName, RunTimeTicks },
- PlayState: { PositionTicks, IsPaused, IsMuted },
- } = session;
-
- const { IsVideoDirect, VideoDecoderIsHardware, VideoEncoderIsHardware } = session?.TranscodingInfo || {
- IsVideoDirect: true,
- VideoDecoderIsHardware: true,
- VideoEncoderIsHardware: true,
- };
-
- const percent = (PositionTicks / RunTimeTicks) * 100;
-
- return (
- <>
-
-
-
- {Name}
- {SeriesName && ` - ${SeriesName}`}
-
-
-
- {IsVideoDirect && }
- {!IsVideoDirect && (!VideoDecoderIsHardware || !VideoEncoderIsHardware) && }
- {!IsVideoDirect && VideoDecoderIsHardware && VideoEncoderIsHardware && (
-
- )}
-
-
-
-
-
-
- {IsPaused && (
- {
- playCommand(session, "Unpause");
- }}
- className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
- />
- )}
- {!IsPaused && (
- {
- playCommand(session, "Pause");
- }}
- className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
- />
- )}
-
-
-
{IsMuted && }
-
- {ticksToString(PositionTicks)}
- /
- {ticksToString(RunTimeTicks)}
-
-
- >
- );
-}
-
-function SessionEntry({ playCommand, session }) {
- const {
- NowPlayingItem: { Name, SeriesName, RunTimeTicks },
- PlayState: { PositionTicks, IsPaused, IsMuted },
- } = session;
-
- const { IsVideoDirect, VideoDecoderIsHardware, VideoEncoderIsHardware } = session?.TranscodingInfo || {};
-
- const percent = (PositionTicks / RunTimeTicks) * 100;
-
- return (
-
-
-
- {IsPaused && (
- {
- playCommand(session, "Unpause");
- }}
- className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
- />
- )}
- {!IsPaused && (
- {
- playCommand(session, "Pause");
- }}
- className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
- />
- )}
-
-
-
- {Name}
- {SeriesName && ` - ${SeriesName}`}
-
-
-
{IsMuted && }
-
{ticksToString(PositionTicks)}
-
- {IsVideoDirect && }
- {!IsVideoDirect && (!VideoDecoderIsHardware || !VideoEncoderIsHardware) && }
- {!IsVideoDirect && VideoDecoderIsHardware && VideoEncoderIsHardware && }
-
-
- );
-}
-
-export default function Emby({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const {
- data: sessionsData,
- error: sessionsError,
- mutate: sessionMutate,
- } = useSWR(formatProxyUrl(config, "Sessions"), {
- refreshInterval: 5000,
- });
-
- async function handlePlayCommand(session, command) {
- const url = formatProxyUrl(config, `Sessions/${session.Id}/Playing/${command}`);
- await fetch(url, {
- method: "POST",
- }).then(() => {
- sessionMutate();
- });
- }
-
- if (sessionsError) {
- return ;
- }
-
- if (!sessionsData) {
- return (
-
- );
- }
-
- const playing = sessionsData
- .filter((session) => session?.NowPlayingItem)
- .sort((a, b) => {
- if (a.PlayState.PositionTicks > b.PlayState.PositionTicks) {
- return 1;
- }
- if (a.PlayState.PositionTicks < b.PlayState.PositionTicks) {
- return -1;
- }
- return 0;
- });
-
- if (playing.length === 0) {
- return (
-
-
- {t("emby.no_active")}
-
-
- -
-
-
- );
- }
-
- if (playing.length === 1) {
- const session = playing[0];
- return (
-
- handlePlayCommand(currentSession, command)}
- session={session}
- />
-
- );
- }
-
- return (
-
- {playing.map((session) => (
- handlePlayCommand(currentSession, command)}
- session={session}
- />
- ))}
-
- );
-}
diff --git a/src/components/services/widgets/service/gotify.jsx b/src/components/services/widgets/service/gotify.jsx
deleted file mode 100644
index 29215574..00000000
--- a/src/components/services/widgets/service/gotify.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Gotify({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- 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 ;
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/jackett.jsx b/src/components/services/widgets/service/jackett.jsx
deleted file mode 100644
index f9cc2dfb..00000000
--- a/src/components/services/widgets/service/jackett.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Jackett({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: indexersData, error: indexersError } = useSWR(formatProxyUrl(config, "indexers"));
-
- if (indexersError) {
- return ;
- }
-
- if (!indexersData) {
- return (
-
-
-
-
- );
- }
-
- const errored = indexersData.filter((indexer) => indexer.last_error);
-
- return (
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/jellyfin.jsx b/src/components/services/widgets/service/jellyfin.jsx
deleted file mode 100644
index 195c133c..00000000
--- a/src/components/services/widgets/service/jellyfin.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import Emby from "./emby";
-
-export default function Jellyfin({ service }) {
- return ;
-}
diff --git a/src/components/services/widgets/service/jellyseerr.jsx b/src/components/services/widgets/service/jellyseerr.jsx
deleted file mode 100644
index f4d5b71e..00000000
--- a/src/components/services/widgets/service/jellyseerr.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Jellyseerr({ 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/components/services/widgets/service/overseerr.jsx b/src/components/services/widgets/service/overseerr.jsx
deleted file mode 100644
index 83417236..00000000
--- a/src/components/services/widgets/service/overseerr.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Overseerr({ 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/components/services/widgets/service/portainer.jsx b/src/components/services/widgets/service/portainer.jsx
deleted file mode 100644
index 1c28333b..00000000
--- a/src/components/services/widgets/service/portainer.jsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Portainer({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: containersData, error: containersError } = useSWR(
- formatProxyUrl(config, `docker/containers/json?all=1`)
- );
-
- if (containersError) {
- return ;
- }
-
- if (!containersData) {
- return (
-
-
-
-
-
- );
- }
-
- if (containersData.error) {
- return ;
- }
-
- const running = containersData.filter((c) => c.State === "running").length;
- const stopped = containersData.filter((c) => c.State === "exited").length;
- const total = containersData.length;
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/prowlarr.jsx b/src/components/services/widgets/service/prowlarr.jsx
deleted file mode 100644
index e80b9e3d..00000000
--- a/src/components/services/widgets/service/prowlarr.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Prowlarr({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: indexersData, error: indexersError } = useSWR(formatProxyUrl(config, "indexer"));
- const { data: grabsData, error: grabsError } = useSWR(formatProxyUrl(config, "indexerstats"));
-
- if (indexersError || grabsError) {
- return ;
- }
-
- if (!indexersData || !grabsData) {
- return (
-
-
-
-
-
-
-
- );
- }
-
- const indexers = indexersData?.filter((indexer) => indexer.enable === true);
-
- 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;
- numberOfFailedQueries += numberOfFailedQueries + element.numberOfFailedQueries;
- });
-
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/qbittorrent.jsx b/src/components/services/widgets/service/qbittorrent.jsx
deleted file mode 100644
index e7030cd8..00000000
--- a/src/components/services/widgets/service/qbittorrent.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function QBittorrent({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: torrentData, error: torrentError } = useSWR(formatProxyUrl(config, "torrents/info"));
-
- if (torrentError) {
- return ;
- }
-
- if (!torrentData) {
- return (
-
-
-
-
-
-
- );
- }
-
- let rateDl = 0;
- let rateUl = 0;
- let completed = 0;
-
- for (let i = 0; i < torrentData.length; i += 1) {
- const torrent = torrentData[i];
- rateDl += torrent.dlspeed;
- rateUl += torrent.upspeed;
- if (torrent.progress === 1) {
- completed += 1;
- }
- }
-
- const leech = torrentData.length - completed;
-
- let unitsDl = "KB/s";
- let unitsUl = "KB/s";
- rateDl /= 1024;
- rateUl /= 1024;
-
- if (rateDl > 1024) {
- rateDl /= 1024;
- unitsDl = "MB/s";
- }
-
- if (rateUl > 1024) {
- rateUl /= 1024;
- unitsUl = "MB/s";
- }
-
- return (
-
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/radarr.jsx b/src/components/services/widgets/service/radarr.jsx
deleted file mode 100644
index f738ab71..00000000
--- a/src/components/services/widgets/service/radarr.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import useSWR from "swr";
-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(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/components/services/widgets/service/readarr.jsx b/src/components/services/widgets/service/readarr.jsx
deleted file mode 100644
index aab6290a..00000000
--- a/src/components/services/widgets/service/readarr.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Readarr({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- 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 ;
- }
-
- if (!booksData || !wantedData || !queueData) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/rutorrent.jsx b/src/components/services/widgets/service/rutorrent.jsx
deleted file mode 100644
index 6aba5e67..00000000
--- a/src/components/services/widgets/service/rutorrent.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Rutorrent({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: statusData, error: statusError } = useSWR(formatProxyUrl(config));
-
- if (statusError) {
- return ;
- }
-
- if (!statusData) {
- return (
-
-
-
-
-
- );
- }
-
- const upload = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_up_rate"], 10), 0);
-
- const download = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_down_rate"], 10), 0);
-
- const active = statusData.filter((torrent) => torrent["d.get_state"] === "1");
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/sabnzbd.jsx b/src/components/services/widgets/service/sabnzbd.jsx
deleted file mode 100644
index a79e11fe..00000000
--- a/src/components/services/widgets/service/sabnzbd.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function SABnzbd({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: queueData, error: queueError } = useSWR(formatProxyUrl(config, "queue"));
-
- if (queueError) {
- return ;
- }
-
- if (!queueData) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/sonarr.jsx b/src/components/services/widgets/service/sonarr.jsx
deleted file mode 100644
index ea91388b..00000000
--- a/src/components/services/widgets/service/sonarr.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Sonarr({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- 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 ;
- }
-
- if (!wantedData || !queuedData || !seriesData) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/speedtest.jsx b/src/components/services/widgets/service/speedtest.jsx
deleted file mode 100644
index def1336e..00000000
--- a/src/components/services/widgets/service/speedtest.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Speedtest({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: speedtestData, error: speedtestError } = useSWR(formatProxyUrl(config, "speedtest/latest"));
-
- if (speedtestError) {
- return ;
- }
-
- if (!speedtestData) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/strelaysrv.jsx b/src/components/services/widgets/service/strelaysrv.jsx
deleted file mode 100644
index a1aff994..00000000
--- a/src/components/services/widgets/service/strelaysrv.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function StRelaySrv({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: statsData, error: statsError } = useSWR(formatProxyUrl(config, `status`));
-
- if (statsError) {
- return ;
- }
-
- if (!statsData) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/tautulli.jsx b/src/components/services/widgets/service/tautulli.jsx
deleted file mode 100644
index 9ba4306f..00000000
--- a/src/components/services/widgets/service/tautulli.jsx
+++ /dev/null
@@ -1,183 +0,0 @@
-/* eslint-disable camelcase */
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-import { BsFillPlayFill, BsPauseFill, BsCpu, BsFillCpuFill } from "react-icons/bs";
-import { MdOutlineSmartDisplay, MdSmartDisplay } from "react-icons/md";
-
-import Widget from "../widget";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-function millisecondsToTime(milliseconds) {
- const seconds = Math.floor((milliseconds / 1000) % 60);
- const minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
- const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
- return { hours, minutes, seconds };
-}
-
-function millisecondsToString(milliseconds) {
- const { hours, minutes, seconds } = millisecondsToTime(milliseconds);
- const parts = [];
- if (hours > 0) {
- parts.push(hours);
- }
- parts.push(minutes);
- parts.push(seconds);
-
- return parts.map((part) => part.toString().padStart(2, "0")).join(":");
-}
-
-function SingleSessionEntry({ session }) {
- const { full_title, duration, view_offset, progress_percent, state, video_decision, audio_decision } = session;
-
- return (
- <>
-
-
-
- {video_decision === "direct play" && audio_decision === "direct play" && (
-
- )}
- {video_decision === "copy" && audio_decision === "copy" && }
- {video_decision !== "copy" &&
- video_decision !== "direct play" &&
- (audio_decision !== "copy" || audio_decision !== "direct play") && }
- {(video_decision === "copy" || video_decision === "direct play") &&
- audio_decision !== "copy" &&
- audio_decision !== "direct play" && }
-
-
-
-
-
-
- {state === "paused" && (
-
- )}
- {state !== "paused" && (
-
- )}
-
-
-
- {millisecondsToString(view_offset)}
- /
- {millisecondsToString(duration)}
-
-
- >
- );
-}
-
-function SessionEntry({ session }) {
- const { full_title, view_offset, progress_percent, state, video_decision, audio_decision } = session;
-
- return (
-
-
-
- {state === "paused" && (
-
- )}
- {state !== "paused" && (
-
- )}
-
-
-
- {video_decision === "direct play" && audio_decision === "direct play" && (
-
- )}
- {video_decision === "copy" && audio_decision === "copy" && }
- {video_decision !== "copy" &&
- video_decision !== "direct play" &&
- (audio_decision !== "copy" || audio_decision !== "direct play") && }
- {(video_decision === "copy" || video_decision === "direct play") &&
- audio_decision !== "copy" &&
- audio_decision !== "direct play" && }
-
-
{millisecondsToString(view_offset)}
-
- );
-}
-
-export default function Tautulli({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: activityData, error: activityError } = useSWR(formatProxyUrl(config, "get_activity"), {
- refreshInterval: 5000,
- });
-
- if (activityError) {
- return ;
- }
-
- if (!activityData) {
- return (
-
- );
- }
-
- const playing = activityData.response.data.sessions.sort((a, b) => {
- if (a.view_offset > b.view_offset) {
- return 1;
- }
- if (a.view_offset < b.view_offset) {
- return -1;
- }
- return 0;
- });
-
- if (playing.length === 0) {
- return (
-
-
- {t("tautulli.no_active")}
-
-
- -
-
-
- );
- }
-
- if (playing.length === 1) {
- const session = playing[0];
- return (
-
-
-
- );
- }
-
- return (
-
- {playing.map((session) => (
-
- ))}
-
- );
-}
diff --git a/src/components/services/widgets/service/traefik.jsx b/src/components/services/widgets/service/traefik.jsx
deleted file mode 100644
index efef287e..00000000
--- a/src/components/services/widgets/service/traefik.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Traefik({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: traefikData, error: traefikError } = useSWR(formatProxyUrl(config, "overview"));
-
- if (traefikError) {
- return ;
- }
-
- if (!traefikData) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- );
-}
diff --git a/src/components/services/widgets/service/transmission.jsx b/src/components/services/widgets/service/transmission.jsx
deleted file mode 100644
index fb449e28..00000000
--- a/src/components/services/widgets/service/transmission.jsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import useSWR from "swr";
-import { useTranslation } from "next-i18next";
-
-import Widget from "../widget";
-import Block from "../block";
-
-import { formatProxyUrl } from "utils/api-helpers";
-
-export default function Transmission({ service }) {
- const { t } = useTranslation();
-
- const config = service.widget;
-
- const { data: torrentData, error: torrentError } = useSWR(formatProxyUrl(config));
-
- if (torrentError) {
- return ;
- }
-
- if (!torrentData) {
- return (
-
-
-
-
-
-
- );
- }
-
- const { torrents } = torrentData.arguments;
- let rateDl = 0;
- let rateUl = 0;
- let completed = 0;
-
- for (let i = 0; i < torrents.length; i += 1) {
- const torrent = torrents[i];
- rateDl += torrent.rateDownload;
- rateUl += torrent.rateUpload;
- if (torrent.percentDone === 1) {
- completed += 1;
- }
- }
-
- const leech = torrents.length - completed;
-
- let unitsDl = "KB/s";
- let unitsUl = "KB/s";
- rateDl /= 1024;
- rateUl /= 1024;
-
- if (rateDl > 1024) {
- rateDl /= 1024;
- unitsDl = "MB/s";
- }
-
- if (rateUl > 1024) {
- rateUl /= 1024;
- unitsUl = "MB/s";
- }
-
- return (
-
-
-
-
-
-
- );
-}
diff --git a/src/utils/api-helpers.js b/src/utils/api-helpers.js
index 9293e987..17e966ec 100644
--- a/src/utils/api-helpers.js
+++ b/src/utils/api-helpers.js
@@ -1,25 +1,5 @@
// const formats = {
-// emby: `{url}/emby/{endpoint}?api_key={key}`,
-// jellyfin: `{url}/emby/{endpoint}?api_key={key}`,
-// pihole: `{url}/admin/{endpoint}`,
-// 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}`,
-// ombi: `{url}/api/v1/{endpoint}`,
-// npm: `{url}/api/{endpoint}`,
// lidarr: `{url}/api/v1/{endpoint}?apikey={key}`,
-// readarr: `{url}/api/v1/{endpoint}?apikey={key}`,
-// sabnzbd: `{url}/api/?apikey={key}&output=json&mode={endpoint}`,
-// gotify: `{url}/{endpoint}`,
-// prowlarr: `{url}/api/v1/{endpoint}`,
-// jackett: `{url}/api/v2.0/{endpoint}?apikey={key}&configured=true`,
-// strelaysrv: `{url}/{endpoint}`,
-// mastodon: `{url}/api/v1/{endpoint}`,
// };
export function formatApiCall(url, args) {
@@ -45,7 +25,7 @@ function getURLSearchParams(widget, endpoint) {
export function formatProxyUrlWithSegments(widget, endpoint, segments) {
const params = getURLSearchParams(widget, endpoint);
if (segments) {
- params.append("segments", JSON.stringify(segments))
+ params.append("segments", JSON.stringify(segments));
}
return `/api/services/proxy?${params.toString()}`;
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 0831ba7d..f4003662 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -10,15 +10,20 @@ const components = {
jackett: dynamic(() => import("./jackett/component")),
jellyfin: dynamic(() => import("./emby/component")),
jellyseerr: dynamic(() => import("./jellyseerr/component")),
+ mastodon: dynamic(() => import("./mastodon/component")),
+ npm: dynamic(() => import("./npm/component")),
+ nzbget: dynamic(() => import("./nzbget/component")),
+ ombi: dynamic(() => import("./ombi/component")),
overseerr: dynamic(() => import("./overseerr/component")),
+ pihole: dynamic(() => import("./pihole/component")),
portainer: dynamic(() => import("./portainer/component")),
prowlarr: dynamic(() => import("./prowlarr/component")),
qbittorrent: dynamic(() => import("./qbittorrent/component")),
radarr: dynamic(() => import("./radarr/component")),
- sonarr: dynamic(() => import("./sonarr/component")),
readarr: dynamic(() => import("./readarr/component")),
rutorrent: dynamic(() => import("./rutorrent/component")),
sabnzbd: dynamic(() => import("./sabnzbd/component")),
+ sonarr: dynamic(() => import("./sonarr/component")),
speedtest: dynamic(() => import("./speedtest/component")),
strelaysrv: dynamic(() => import("./strelaysrv/component")),
tautulli: dynamic(() => import("./tautulli/component")),
diff --git a/src/components/services/widgets/service/mastodon.jsx b/src/widgets/mastodon/component.jsx
similarity index 86%
rename from src/components/services/widgets/service/mastodon.jsx
rename to src/widgets/mastodon/component.jsx
index d1bb2252..fbee420f 100644
--- a/src/components/services/widgets/service/mastodon.jsx
+++ b/src/widgets/mastodon/component.jsx
@@ -1,12 +1,11 @@
import useSWR from "swr";
import { useTranslation } from "next-i18next";
-import Widget from "../widget";
-import Block from "../block";
-
+import Widget from "components/services/widgets/widget";
+import Block from "components/services/widgets/block";
import { formatProxyUrl } from "utils/api-helpers";
-export default function Mastodon({ service }) {
+export default function Component({ service }) {
const { t } = useTranslation();
const config = service.widget;
diff --git a/src/widgets/mastodon/widget.js b/src/widgets/mastodon/widget.js
new file mode 100644
index 00000000..c9761c5e
--- /dev/null
+++ b/src/widgets/mastodon/widget.js
@@ -0,0 +1,14 @@
+import genericProxyHandler from "utils/proxies/generic";
+
+const widget = {
+ api: "{url}/api/v1/{endpoint}",
+ proxyHandler: genericProxyHandler,
+
+ mappings: {
+ instance: {
+ endpoint: "instance",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/components/services/widgets/service/npm.jsx b/src/widgets/npm/component.jsx
similarity index 85%
rename from src/components/services/widgets/service/npm.jsx
rename to src/widgets/npm/component.jsx
index 93ecf26b..59a709e8 100644
--- a/src/components/services/widgets/service/npm.jsx
+++ b/src/widgets/npm/component.jsx
@@ -1,12 +1,11 @@
import useSWR from "swr";
import { useTranslation } from "next-i18next";
-import Widget from "../widget";
-import Block from "../block";
-
+import Widget from "components/services/widgets/widget";
+import Block from "components/services/widgets/block";
import { formatProxyUrl } from "utils/api-helpers";
-export default function Npm({ service }) {
+export default function Component({ service }) {
const { t } = useTranslation();
const config = service.widget;
diff --git a/src/utils/proxies/npm.js b/src/widgets/npm/proxy.js
similarity index 100%
rename from src/utils/proxies/npm.js
rename to src/widgets/npm/proxy.js
diff --git a/src/widgets/npm/widget.js b/src/widgets/npm/widget.js
new file mode 100644
index 00000000..652cb4a2
--- /dev/null
+++ b/src/widgets/npm/widget.js
@@ -0,0 +1,8 @@
+import npmProxyHandler from "./proxy";
+
+const widget = {
+ api: "{url}/api/{endpoint}",
+ proxyHandler: npmProxyHandler,
+};
+
+export default widget;
diff --git a/src/components/services/widgets/service/nzbget.jsx b/src/widgets/nzbget/component.jsx
similarity index 86%
rename from src/components/services/widgets/service/nzbget.jsx
rename to src/widgets/nzbget/component.jsx
index 58d08850..fe85cdb5 100644
--- a/src/components/services/widgets/service/nzbget.jsx
+++ b/src/widgets/nzbget/component.jsx
@@ -1,12 +1,11 @@
import useSWR from "swr";
import { useTranslation } from "next-i18next";
-import Widget from "../widget";
-import Block from "../block";
-
+import Widget from "components/services/widgets/widget";
+import Block from "components/services/widgets/block";
import { formatProxyUrl } from "utils/api-helpers";
-export default function Nzbget({ service }) {
+export default function Component({ service }) {
const { t } = useTranslation("common");
const config = service.widget;
diff --git a/src/utils/proxies/nzbget.js b/src/widgets/nzbget/proxy.js
similarity index 100%
rename from src/utils/proxies/nzbget.js
rename to src/widgets/nzbget/proxy.js
diff --git a/src/widgets/nzbget/widget.js b/src/widgets/nzbget/widget.js
new file mode 100644
index 00000000..975c8dea
--- /dev/null
+++ b/src/widgets/nzbget/widget.js
@@ -0,0 +1,7 @@
+import nzbgetProxyHandler from "./proxy";
+
+const widget = {
+ proxyHandler: nzbgetProxyHandler,
+};
+
+export default widget;
diff --git a/src/components/services/widgets/service/ombi.jsx b/src/widgets/ombi/component.jsx
similarity index 83%
rename from src/components/services/widgets/service/ombi.jsx
rename to src/widgets/ombi/component.jsx
index 887c7348..55435b54 100644
--- a/src/components/services/widgets/service/ombi.jsx
+++ b/src/widgets/ombi/component.jsx
@@ -1,12 +1,11 @@
import useSWR from "swr";
import { useTranslation } from "next-i18next";
-import Widget from "../widget";
-import Block from "../block";
-
+import Widget from "components/services/widgets/widget";
+import Block from "components/services/widgets/block";
import { formatProxyUrl } from "utils/api-helpers";
-export default function Ombi({ service }) {
+export default function Component({ service }) {
const { t } = useTranslation();
const config = service.widget;
diff --git a/src/widgets/ombi/widget.js b/src/widgets/ombi/widget.js
new file mode 100644
index 00000000..d0dbea93
--- /dev/null
+++ b/src/widgets/ombi/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/components/services/widgets/service/pihole.jsx b/src/widgets/pihole/component.jsx
similarity index 85%
rename from src/components/services/widgets/service/pihole.jsx
rename to src/widgets/pihole/component.jsx
index 720cd681..fea8e68c 100644
--- a/src/components/services/widgets/service/pihole.jsx
+++ b/src/widgets/pihole/component.jsx
@@ -1,12 +1,11 @@
import useSWR from "swr";
import { useTranslation } from "next-i18next";
-import Widget from "../widget";
-import Block from "../block";
-
+import Widget from "components/services/widgets/widget";
+import Block from "components/services/widgets/block";
import { formatProxyUrl } from "utils/api-helpers";
-export default function Pihole({ service }) {
+export default function Component({ service }) {
const { t } = useTranslation();
const config = service.widget;
diff --git a/src/widgets/pihole/widget.js b/src/widgets/pihole/widget.js
new file mode 100644
index 00000000..5198ea2c
--- /dev/null
+++ b/src/widgets/pihole/widget.js
@@ -0,0 +1,14 @@
+import genericProxyHandler from "utils/proxies/generic";
+
+const widget = {
+ api: "{url}/admin/{endpoint}",
+ proxyHandler: genericProxyHandler,
+
+ mappings: {
+ "api.php": {
+ endpoint: "api.php",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 241c78d9..8466a2c5 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -5,15 +5,20 @@ import emby from "./emby/widget";
import gotify from "./gotify/widget";
import jackett from "./jackett/widget";
import jellyseerr from "./jellyseerr/widget";
+import mastodon from "./mastodon/widget";
+import npm from "./npm/widget";
+import nzbget from "./nzbget/widget";
+import ombi from "./ombi/widget";
import overseerr from "./overseerr/widget";
+import pihole from "./pihole/widget";
import portainer from "./portainer/widget";
import prowlarr from "./prowlarr/widget";
import qbittorrent from "./qbittorrent/widget";
import radarr from "./radarr/widget";
-import sonarr from "./sonarr/widget";
import readarr from "./readarr/widget";
import rutorrent from "./rutorrent/widget";
import sabnzbd from "./sabnzbd/widget";
+import sonarr from "./sonarr/widget";
import speedtest from "./speedtest/widget";
import strelaysrv from "./strelaysrv/widget";
import tautulli from "./tautulli/widget";
@@ -29,15 +34,20 @@ const widgets = {
jackett,
jellyfin: emby,
jellyseerr,
+ mastodon,
+ npm,
+ nzbget,
+ ombi,
overseerr,
+ pihole,
portainer,
prowlarr,
qbittorrent,
radarr,
- sonarr,
readarr,
rutorrent,
sabnzbd,
+ sonarr,
speedtest,
strelaysrv,
tautulli,