widget refactoring

pull/302/head
Ben Phelps 2 years ago
parent 03fa2f86d7
commit 035dd25ece

@ -1,5 +1,3 @@
import { URLSearchParams } from "next/dist/compiled/@edge-runtime/primitives/url";
import createLogger from "utils/logger";
import genericProxyHandler from "utils/proxies/generic";
import widgets from "widgets/widgets";
@ -35,10 +33,9 @@ export default async function handler(req, res) {
if (req.query.params) {
const queryParams = JSON.parse(req.query.params);
const query = new URLSearchParams(mappingParams.map(p => [p, queryParams[p]]));
const query = new URLSearchParams(mappingParams.map((p) => [p, queryParams[p]]));
req.query.endpoint = `${endpoint}?${query}`;
}
else {
} else {
req.query.endpoint = endpoint;
}

@ -5,8 +5,19 @@ const components = {
bazarr: dynamic(() => import("./bazarr/component")),
coinmarketcap: dynamic(() => import("./coinmarketcap/component")),
overseerr: dynamic(() => import("./overseerr/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")),
speedtest: dynamic(() => import("./speedtest/component")),
strelaysrv: dynamic(() => import("./strelaysrv/component")),
tautulli: dynamic(() => import("./tautulli/component")),
traefik: dynamic(() => import("./traefik/component")),
transmission: dynamic(() => import("./transmission/component")),
};
export default components;

@ -0,0 +1,48 @@
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: containersData, error: containersError } = useSWR(
formatProxyUrl(config, `docker/containers/json`, {
all: 1,
})
);
if (containersError) {
return <Widget error={t("widget.api_error")} />;
}
if (!containersData) {
return (
<Widget>
<Block label={t("portainer.running")} />
<Block label={t("portainer.stopped")} />
<Block label={t("portainer.total")} />
</Widget>
);
}
if (containersData.error) {
return <Widget error={t("widget.api_error")} />;
}
const running = containersData.filter((c) => c.State === "running").length;
const stopped = containersData.filter((c) => c.State === "exited").length;
const total = containersData.length;
return (
<Widget>
<Block label={t("portainer.running")} value={running} />
<Block label={t("portainer.stopped")} value={stopped} />
<Block label={t("portainer.total")} value={total} />
</Widget>
);
}

@ -0,0 +1,15 @@
import genericProxyHandler from "utils/proxies/generic";
const widget = {
api: "{url}/api/endpoints/{env}/{endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
"docker/containers/json": {
endpoint: "docker/containers/json",
params: ["all"],
},
},
};
export default widget;

@ -0,0 +1,54 @@
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: indexersData, error: indexersError } = useSWR(formatProxyUrl(config, "indexer"));
const { data: grabsData, error: grabsError } = useSWR(formatProxyUrl(config, "indexerstats"));
if (indexersError || grabsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!indexersData || !grabsData) {
return (
<Widget>
<Block label={t("prowlarr.enableIndexers")} />
<Block label={t("prowlarr.numberOfGrabs")} />
<Block label={t("prowlarr.numberOfQueries")} />
<Block label={t("prowlarr.numberOfFailGrabs")} />
<Block label={t("prowlarr.numberOfFailQueries")} />
</Widget>
);
}
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 (
<Widget>
<Block label={t("prowlarr.enableIndexers")} value={indexers.length} />
<Block label={t("prowlarr.numberOfGrabs")} value={numberOfGrabs} />
<Block label={t("prowlarr.numberOfQueries")} value={numberOfQueries} />
<Block label={t("prowlarr.numberOfFailGrabs")} value={numberOfFailedGrabs} />
<Block label={t("prowlarr.numberOfFailQueries")} value={numberOfFailedQueries} />
</Widget>
);
}

@ -0,0 +1,17 @@
import genericProxyHandler from "utils/proxies/generic";
const widget = {
api: "{url}/api/v1/{endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
indexer: {
endpoint: "indexer",
},
indexerstats: {
endpoint: "indexerstats",
},
},
};
export default widget;

@ -0,0 +1,68 @@
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: torrentData, error: torrentError } = useSWR(formatProxyUrl(config, "torrents/info"));
if (torrentError) {
return <Widget error={t("widget.api_error")} />;
}
if (!torrentData) {
return (
<Widget>
<Block label={t("qbittorrent.leech")} />
<Block label={t("qbittorrent.download")} />
<Block label={t("qbittorrent.seed")} />
<Block label={t("qbittorrent.upload")} />
</Widget>
);
}
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 (
<Widget>
<Block label={t("qbittorrent.leech")} value={t("common.number", { value: leech })} />
<Block label={t("qbittorrent.download")} value={`${rateDl.toFixed(2)} ${unitsDl}`} />
<Block label={t("qbittorrent.seed")} value={t("common.number", { value: completed })} />
<Block label={t("qbittorrent.upload")} value={`${rateUl.toFixed(2)} ${unitsUl}`} />
</Widget>
);
}

@ -0,0 +1,8 @@
import qbittorrentProxyHandler from "./proxy";
const widget = {
api: "{url}/api/v2/{endpoint}",
proxyHandler: qbittorrentProxyHandler,
};
export default widget;

@ -0,0 +1,38 @@
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: 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 <Widget error={t("widget.api_error")} />;
}
if (!booksData || !wantedData || !queueData) {
return (
<Widget>
<Block label={t("readarr.wanted")} />
<Block label={t("readarr.queued")} />
<Block label={t("readarr.books")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("readarr.wanted")} value={t("common.number", { value: wantedData.totalRecords })} />
<Block label={t("readarr.queued")} value={t("common.number", { value: queueData.totalCount })} />
<Block label={t("readarr.books")} value={t("common.number", { value: booksData.have })} />
</Widget>
);
}

@ -0,0 +1,24 @@
import genericProxyHandler from "utils/proxies/generic";
import { jsonArrayFilter } from "utils/api-helpers";
const widget = {
api: "{url}/api/v1/{endpoint}?apikey={key}",
proxyHandler: genericProxyHandler,
mappings: {
book: {
endpoint: "book",
map: (data) => ({
have: jsonArrayFilter(data, (item) => item?.statistics?.bookFileCount > 0).length,
}),
},
"queue/status": {
endpoint: "queue/status",
},
"wanted/missing": {
endpoint: "wanted/missing",
},
},
};
export default widget;

@ -0,0 +1,42 @@
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: statusData, error: statusError } = useSWR(formatProxyUrl(config));
if (statusError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statusData) {
return (
<Widget>
<Block label={t("rutorrent.active")} />
<Block label={t("rutorrent.upload")} />
<Block label={t("rutorrent.download")} />
</Widget>
);
}
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 (
<Widget>
<Block label={t("rutorrent.active")} value={active.length} />
<Block label={t("rutorrent.upload")} value={t("common.bitrate", { value: upload })} />
<Block label={t("rutorrent.download")} value={t("common.bitrate", { value: download })} />
</Widget>
);
}

@ -0,0 +1,8 @@
import rutorrentProxyHandler from "./proxy";
const widget = {
api: "{url}/plugins/httprpc/action.php",
proxyHandler: rutorrentProxyHandler,
};
export default widget;

@ -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: queueData, error: queueError } = useSWR(formatProxyUrl(config, "queue"));
if (queueError) {
return <Widget error={t("widget.api_error")} />;
}
if (!queueData) {
return (
<Widget>
<Block label={t("sabnzbd.rate")} />
<Block label={t("sabnzbd.queue")} />
<Block label={t("sabnzbd.timeleft")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("sabnzbd.rate")} value={`${queueData.queue.speed}B/s`} />
<Block label={t("sabnzbd.queue")} value={t("common.number", { value: queueData.queue.noofslots })} />
<Block label={t("sabnzbd.timeleft")} value={queueData.queue.timeleft} />
</Widget>
);
}

@ -0,0 +1,14 @@
import genericProxyHandler from "utils/proxies/generic";
const widget = {
api: "{url}/api/?apikey={key}&output=json&mode={endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
queue: {
endpoint: "queue",
},
},
};
export default widget;

@ -6,19 +6,19 @@ const widget = {
proxyHandler: genericProxyHandler,
mappings: {
"series": {
series: {
endpoint: "series",
map: (data) => ({
total: asJson(data).length,
}),
},
"queue": {
queue: {
endpoint: "queue",
},
"wanted/missing": {
endpoint: "wanted/missing",
},
endpoint: "wanted/missing",
},
},
};
export default widget;

@ -0,0 +1,45 @@
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: speedtestData, error: speedtestError } = useSWR(formatProxyUrl(config, "speedtest/latest"));
if (speedtestError) {
return <Widget error={t("widget.api_error")} />;
}
if (!speedtestData) {
return (
<Widget>
<Block label={t("speedtest.download")} />
<Block label={t("speedtest.upload")} />
<Block label={t("speedtest.ping")} />
</Widget>
);
}
return (
<Widget>
<Block
label={t("speedtest.download")}
value={t("common.bitrate", { value: speedtestData.data.download * 1024 * 1024 })}
/>
<Block
label={t("speedtest.upload")}
value={t("common.bitrate", { value: speedtestData.data.upload * 1024 * 1024 })}
/>
<Block
label={t("speedtest.ping")}
value={t("common.ms", { value: speedtestData.data.ping, style: "unit", unit: "millisecond" })}
/>
</Widget>
);
}

@ -0,0 +1,14 @@
import genericProxyHandler from "utils/proxies/generic";
const widget = {
api: "{url}/api/{endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
"speedtest/latest": {
endpoint: "speedtest/latest",
},
},
};
export default widget;

@ -0,0 +1,43 @@
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, `status`));
if (statsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
return (
<Widget>
<Block label={t("strelaysrv.numActiveSessions")} />
<Block label={t("strelaysrv.numConnections")} />
<Block label={t("strelaysrv.bytesProxied")} />
</Widget>
);
}
return (
<Widget>
<Block
label={t("strelaysrv.numActiveSessions")}
value={t("common.number", { value: statsData.numActiveSessions })}
/>
<Block label={t("strelaysrv.numConnections")} value={t("common.number", { value: statsData.numConnections })} />
<Block label={t("strelaysrv.dataRelayed")} value={t("common.bytes", { value: statsData.bytesProxied })} />
<Block
label={t("strelaysrv.transferRate")}
value={t("common.bitrate", { value: statsData.kbps10s1m5m15m30m60m[5] })}
/>
</Widget>
);
}

@ -0,0 +1,14 @@
import genericProxyHandler from "utils/proxies/generic";
const widget = {
api: "{url}/{endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
status: {
endpoint: "status",
},
},
};
export default widget;

@ -0,0 +1,182 @@
/* 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 "components/services/widgets/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 (
<>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div className="text-xs z-10 self-center ml-2 relative w-full h-4 grow mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">{full_title}</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1">
{video_decision === "direct play" && audio_decision === "direct play" && (
<MdSmartDisplay className="opacity-50" />
)}
{video_decision === "copy" && audio_decision === "copy" && <MdOutlineSmartDisplay className="opacity-50" />}
{video_decision !== "copy" &&
video_decision !== "direct play" &&
(audio_decision !== "copy" || audio_decision !== "direct play") && <BsFillCpuFill className="opacity-50" />}
{(video_decision === "copy" || video_decision === "direct play") &&
audio_decision !== "copy" &&
audio_decision !== "direct play" && <BsCpu className="opacity-50" />}
</div>
</div>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div
className="absolute h-5 rounded-md bg-theme-200 dark:bg-theme-900/40 z-0"
style={{
width: `${progress_percent}%`,
}}
/>
<div className="text-xs z-10 self-center ml-1">
{state === "paused" && (
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
{state !== "paused" && (
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
</div>
<div className="grow " />
<div className="self-center text-xs flex justify-end mr-2 z-10">
{millisecondsToString(view_offset)}
<span className="mx-0.5 text-[8px]">/</span>
{millisecondsToString(duration)}
</div>
</div>
</>
);
}
function SessionEntry({ session }) {
const { full_title, view_offset, progress_percent, state, video_decision, audio_decision } = session;
return (
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div
className="absolute h-5 rounded-md bg-theme-200 dark:bg-theme-900/40 z-0"
style={{
width: `${progress_percent}%`,
}}
/>
<div className="text-xs z-10 self-center ml-1">
{state === "paused" && (
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
{state !== "paused" && (
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
</div>
<div className="text-xs z-10 self-center ml-2 relative w-full h-4 grow mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">{full_title}</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1 z-10">
{video_decision === "direct play" && audio_decision === "direct play" && (
<MdSmartDisplay className="opacity-50" />
)}
{video_decision === "copy" && audio_decision === "copy" && <MdOutlineSmartDisplay className="opacity-50" />}
{video_decision !== "copy" &&
video_decision !== "direct play" &&
(audio_decision !== "copy" || audio_decision !== "direct play") && <BsFillCpuFill className="opacity-50" />}
{(video_decision === "copy" || video_decision === "direct play") &&
audio_decision !== "copy" &&
audio_decision !== "direct play" && <BsCpu className="opacity-50" />}
</div>
<div className="self-center text-xs flex justify-end mr-2 z-10">{millisecondsToString(view_offset)}</div>
</div>
);
}
export default function Component({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: activityData, error: activityError } = useSWR(formatProxyUrl(config, "get_activity"), {
refreshInterval: 5000,
});
if (activityError) {
return <Widget error={t("widget.api_error")} />;
}
if (!activityData) {
return (
<div className="flex flex-col pb-1 mx-1">
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
</div>
);
}
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 (
<div className="flex flex-col pb-1 mx-1">
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">{t("tautulli.no_active")}</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
</div>
);
}
if (playing.length === 1) {
const session = playing[0];
return (
<div className="flex flex-col pb-1 mx-1">
<SingleSessionEntry session={session} />
</div>
);
}
return (
<div className="flex flex-col pb-1 mx-1">
{playing.map((session) => (
<SessionEntry key={session.Id} session={session} />
))}
</div>
);
}

@ -0,0 +1,14 @@
import genericProxyHandler from "utils/proxies/generic";
const widget = {
api: "{url}/api/v2?apikey={key}&cmd={endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
get_activity: {
endpoint: "get_activity",
},
},
};
export default widget;

@ -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: traefikData, error: traefikError } = useSWR(formatProxyUrl(config, "overview"));
if (traefikError) {
return <Widget error={t("widget.api_error")} />;
}
if (!traefikData) {
return (
<Widget>
<Block label={t("traefik.routers")} />
<Block label={t("traefik.services")} />
<Block label={t("traefik.middleware")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("traefik.routers")} value={traefikData.http.routers.total} />
<Block label={t("traefik.services")} value={traefikData.http.services.total} />
<Block label={t("traefik.middleware")} value={traefikData.http.middlewares.total} />
</Widget>
);
}

@ -0,0 +1,14 @@
import genericProxyHandler from "utils/proxies/generic";
const widget = {
api: "{url}/api/{endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
overview: {
endpoint: "overview",
},
},
};
export default widget;

@ -0,0 +1,69 @@
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: torrentData, error: torrentError } = useSWR(formatProxyUrl(config));
if (torrentError) {
return <Widget error={t("widget.api_error")} />;
}
if (!torrentData) {
return (
<Widget>
<Block label={t("transmission.leech")} />
<Block label={t("transmission.download")} />
<Block label={t("transmission.seed")} />
<Block label={t("transmission.upload")} />
</Widget>
);
}
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 (
<Widget>
<Block label={t("transmission.leech")} value={t("common.number", { value: leech })} />
<Block label={t("transmission.download")} value={`${rateDl.toFixed(2)} ${unitsDl}`} />
<Block label={t("transmission.seed")} value={t("common.number", { value: completed })} />
<Block label={t("transmission.upload")} value={`${rateUl.toFixed(2)} ${unitsUl}`} />
</Widget>
);
}

@ -0,0 +1,8 @@
import transmissionProxyHandler from "./proxy";
const widget = {
api: "{url}/transmission/rpc",
proxyHandler: transmissionProxyHandler,
};
export default widget;

@ -2,16 +2,38 @@ import adguard from "./adguard/widget";
import bazarr from "./bazarr/widget";
import coinmarketcap from "./coinmarketcap/widget";
import overseerr from "./overseerr/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 sonarr from "./sonarr/widget";
import readarr from "./readarr/widget";
import rutorrent from "./rutorrent/widget";
import sabnzbd from "./sabnzbd/widget";
import speedtest from "./speedtest/widget";
import strelaysrv from "./strelaysrv/widget";
import tautulli from "./tautulli/widget";
import traefik from "./traefik/widget";
import transmission from "./transmission/widget";
const widgets = {
adguard,
bazarr,
coinmarketcap,
overseerr,
portainer,
prowlarr,
qbittorrent,
radarr,
sonarr,
readarr,
rutorrent,
sabnzbd,
speedtest,
strelaysrv,
tautulli,
traefik,
transmission,
};
export default widgets;

Loading…
Cancel
Save