From 94e9d66becd159443376dcbdc83dcfebc5f64842 Mon Sep 17 00:00:00 2001 From: Ben Phelps Date: Thu, 25 Aug 2022 16:29:26 +0300 Subject: [PATCH] refactor service widgets --- src/components/services/item.jsx | 5 +- src/components/services/stats/list.jsx | 70 ---------------- src/components/services/stats/stat.jsx | 8 -- src/components/services/widget.jsx | 14 ++-- src/components/services/widgets/block.jsx | 8 ++ src/components/services/widgets/emby.jsx | 64 -------------- src/components/services/widgets/nzbget.jsx | 83 ------------------- src/components/services/widgets/ombi.jsx | 71 ---------------- src/components/services/widgets/portainer.jsx | 81 ------------------ src/components/services/widgets/radarr.jsx | 63 -------------- .../services/widgets/service/docker.jsx | 56 +++++++++++++ .../services/widgets/service/emby.jsx | 45 ++++++++++ .../services/widgets/service/nzbget.jsx | 64 ++++++++++++++ .../services/widgets/service/ombi.jsx | 49 +++++++++++ .../services/widgets/service/portainer.jsx | 59 +++++++++++++ .../services/widgets/service/radarr.jsx | 41 +++++++++ .../services/widgets/service/sonarr.jsx | 39 +++++++++ src/components/services/widgets/sonarr.jsx | 53 ------------ src/components/services/widgets/widget.jsx | 11 +++ 19 files changed, 382 insertions(+), 502 deletions(-) delete mode 100644 src/components/services/stats/list.jsx delete mode 100644 src/components/services/stats/stat.jsx create mode 100644 src/components/services/widgets/block.jsx delete mode 100644 src/components/services/widgets/emby.jsx delete mode 100644 src/components/services/widgets/nzbget.jsx delete mode 100644 src/components/services/widgets/ombi.jsx delete mode 100644 src/components/services/widgets/portainer.jsx delete mode 100644 src/components/services/widgets/radarr.jsx create mode 100644 src/components/services/widgets/service/docker.jsx create mode 100644 src/components/services/widgets/service/emby.jsx create mode 100644 src/components/services/widgets/service/nzbget.jsx create mode 100644 src/components/services/widgets/service/ombi.jsx create mode 100644 src/components/services/widgets/service/portainer.jsx create mode 100644 src/components/services/widgets/service/radarr.jsx create mode 100644 src/components/services/widgets/service/sonarr.jsx delete mode 100644 src/components/services/widgets/sonarr.jsx create mode 100644 src/components/services/widgets/widget.jsx diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index cf85d2794..162d616eb 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -2,12 +2,11 @@ import Image from "next/future/image"; import { useState } from "react"; import { Disclosure, Transition } from "@headlessui/react"; -import StatsList from "./stats/list"; import Status from "./status"; import Widget from "./widget"; +import Docker from "./widgets/service/docker"; export default function Item({ service }) { - const [statsOpen, setStatsOpen] = useState(false); return (
  • @@ -46,7 +45,7 @@ export default function Item({ service }) {
    - +
    diff --git a/src/components/services/stats/list.jsx b/src/components/services/stats/list.jsx deleted file mode 100644 index cb2cdfaa1..000000000 --- a/src/components/services/stats/list.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import useSWR from "swr"; -import { calculateCPUPercent, formatBytes } from "utils/stats-helpers"; -import Stat from "./stat"; - -export default function Stats({ service }) { - // fast - const { data: statusData, error: statusError } = useSWR( - `/api/docker/status/${service.container}/${service.server || ""}`, - { - refreshInterval: 1500, - } - ); - - // takes a full second to collect stats - const { data: statsData, error: statsError } = useSWR( - `/api/docker/stats/${service.container}/${service.server || ""}`, - { - refreshInterval: 1500, - } - ); - - // handle errors first - if (statsError || statusError) { - return ( -
    - -
    - ); - } - - // handle the case where we get a docker error - if (statusData.status !== "running") { - return ( -
    - -
    - ); - } - - // handle the case where the container is offline - if (statusData.status !== "running") { - return ( -
    - -
    - ); - } - - // handle the case where we don't have anything yet - if (!statsData || !statusData) { - return ( -
    - - - - -
    - ); - } - - // we have stats and the container is running - return ( -
    - - - - -
    - ); -} diff --git a/src/components/services/stats/stat.jsx b/src/components/services/stats/stat.jsx deleted file mode 100644 index 57674d4c8..000000000 --- a/src/components/services/stats/stat.jsx +++ /dev/null @@ -1,8 +0,0 @@ -export default function Stat({ value, label }) { - return ( -
    -
    {value}
    -
    {label}
    -
    - ); -} diff --git a/src/components/services/widget.jsx b/src/components/services/widget.jsx index ccbb91762..62df93019 100644 --- a/src/components/services/widget.jsx +++ b/src/components/services/widget.jsx @@ -1,11 +1,13 @@ -import Sonarr from "./widgets/sonarr"; -import Radarr from "./widgets/radarr"; -import Ombi from "./widgets/ombi"; -import Portainer from "./widgets/portainer"; -import Emby from "./widgets/emby"; -import Nzbget from "./widgets/nzbget"; +import Sonarr from "./widgets/service/sonarr"; +import Radarr from "./widgets/service/radarr"; +import Ombi from "./widgets/service/ombi"; +import Portainer from "./widgets/service/portainer"; +import Emby from "./widgets/service/emby"; +import Nzbget from "./widgets/service/nzbget"; +import Docker from "./widgets/service/docker"; const widgetMappings = { + docker: Docker, sonarr: Sonarr, radarr: Radarr, ombi: Ombi, diff --git a/src/components/services/widgets/block.jsx b/src/components/services/widgets/block.jsx new file mode 100644 index 000000000..e0c1858a7 --- /dev/null +++ b/src/components/services/widgets/block.jsx @@ -0,0 +1,8 @@ +export default function Block({ value, label }) { + return ( +
    +
    {value === undefined || value === null ? "-" : value}
    +
    {label}
    +
    + ); +} diff --git a/src/components/services/widgets/emby.jsx b/src/components/services/widgets/emby.jsx deleted file mode 100644 index 0b8d1eb3e..000000000 --- a/src/components/services/widgets/emby.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import useSWR from "swr"; - -export default function Emby({ service }) { - const config = service.widget; - - function buildApiUrl(endpoint) { - const { url, key } = config; - return `${url}/emby/${endpoint}?api_key=${key}`; - } - - const { data: sessionsData, error: sessionsError } = useSWR(buildApiUrl(`Sessions`), { - refreshInterval: 1000, - }); - - if (sessionsError) { - return ( -
    -
    Emby API Error
    -
    - ); - } - - if (!sessionsData) { - return ( -
    -
    -
    -
    -
    PLAYING
    -
    -
    -
    -
    -
    TRANSCODE
    -
    -
    -
    -
    -
    BITRATE
    -
    -
    - ); - } - - const playing = sessionsData.filter((session) => session.hasOwnProperty("NowPlayingItem")); - const transcoding = sessionsData.filter( - (session) => session.hasOwnProperty("PlayState") && session.PlayState.PlayMethod === "Transcode" - ); - const bitrate = playing.reduce((acc, session) => acc + session.NowPlayingItem.Bitrate, 0); - - return ( -
    -
    -
    {playing.length}
    -
    PLAYING
    -
    -
    -
    {transcoding.length}
    -
    TRANSCODE
    -
    -
    -
    {Math.round((bitrate / 1024 / 1024) * 100) / 100} Mbps
    -
    BITRATE
    -
    -
    - ); -} diff --git a/src/components/services/widgets/nzbget.jsx b/src/components/services/widgets/nzbget.jsx deleted file mode 100644 index ec79cb76d..000000000 --- a/src/components/services/widgets/nzbget.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import useSWR from "swr"; -import { JSONRPCClient } from "json-rpc-2.0"; - -import { formatBytes } from "utils/stats-helpers"; - -export default function Nzbget({ service }) { - const config = service.widget; - - const constructedUrl = new URL(config.url); - constructedUrl.pathname = "jsonrpc"; - - const client = new JSONRPCClient((jsonRPCRequest) => - fetch(constructedUrl.toString(), { - method: "POST", - headers: { - "content-type": "application/json", - authorization: `Basic ${btoa(`${config.username}:${config.password}`)}`, - }, - body: JSON.stringify(jsonRPCRequest), - }).then(async (response) => { - if (response.status === 200) { - const jsonRPCResponse = await response.json(); - return client.receive(jsonRPCResponse); - } else if (jsonRPCRequest.id !== undefined) { - return Promise.reject(new Error(response.statusText)); - } - }) - ); - - const { data: statusData, error: statusError } = useSWR( - "status", - (resource) => { - return client.request(resource).then((response) => response); - }, - { - refreshInterval: 1000, - } - ); - - if (statusError) { - return ( -
    -
    Nzbget API Error
    -
    - ); - } - - if (!statusData) { - return ( -
    -
    -
    -
    -
    RATE
    -
    -
    -
    -
    -
    REMAINING
    -
    -
    -
    -
    -
    DOWNLOADED
    -
    -
    - ); - } - - return ( -
    -
    -
    {formatBytes(statusData.DownloadRate)}/s
    -
    RATE
    -
    -
    -
    {Math.round((statusData.RemainingSizeMB / 1024) * 100) / 100} GB
    -
    REMAINING
    -
    -
    -
    {Math.round((statusData.DownloadedSizeMB / 1024) * 100) / 100} GB
    -
    DOWNLOADED
    -
    -
    - ); -} diff --git a/src/components/services/widgets/ombi.jsx b/src/components/services/widgets/ombi.jsx deleted file mode 100644 index e5527cb2a..000000000 --- a/src/components/services/widgets/ombi.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import useSWR from "swr"; - -export default function Ombi({ service }) { - const config = service.widget; - - function buildApiUrl(endpoint) { - const { url } = config; - return `${url}/api/v1/${endpoint}`; - } - - const fetcher = (url) => { - return fetch(url, { - method: "GET", - withCredentials: true, - credentials: "include", - headers: { - ApiKey: `${config.key}`, - "Content-Type": "application/json", - }, - }).then((res) => res.json()); - }; - - const { data: statsData, error: statsError } = useSWR( - buildApiUrl(`Request/count`), - fetcher - ); - - if (statsError) { - return ( -
    -
    Ombi API Error
    -
    - ); - } - - if (!statsData) { - return ( -
    -
    -
    -
    -
    COMPLETED
    -
    -
    -
    -
    -
    QUEUED
    -
    -
    -
    -
    -
    TOTAL
    -
    -
    - ); - } - - return ( -
    -
    -
    {statsData.pending}
    -
    PENDING
    -
    -
    -
    {statsData.approved}
    -
    APPROVED
    -
    -
    -
    {statsData.available}
    -
    AVAILABLE
    -
    -
    - ); -} diff --git a/src/components/services/widgets/portainer.jsx b/src/components/services/widgets/portainer.jsx deleted file mode 100644 index 7af630dc2..000000000 --- a/src/components/services/widgets/portainer.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import useSWR from "swr"; - -export default function Portainer({ service }) { - const config = service.widget; - - function buildApiUrl(endpoint) { - const { url, env } = config; - const reqUrl = new URL(`/api/endpoints/${env}/${endpoint}`, url); - return `/api/proxy?url=${encodeURIComponent(reqUrl)}`; - } - - const fetcher = (url) => { - return fetch(url, { - method: "GET", - withCredentials: true, - credentials: "include", - headers: { - "X-API-Key": `${config.key}`, - "Content-Type": "application/json", - }, - }).then((res) => res.json()); - }; - - const { data: containersData, error: containersError } = useSWR(buildApiUrl(`docker/containers/json`), fetcher); - - if (containersError) { - return ( -
    -
    Portainer API Error
    -
    - ); - } - - if (!containersData) { - return ( -
    -
    -
    -
    -
    RUNNING
    -
    -
    -
    -
    -
    STOPPED
    -
    -
    -
    -
    -
    TOTAL
    -
    -
    - ); - } - - if (containersData.error) { - return ( -
    -
    Portainer 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 ( -
    -
    -
    {running}
    -
    RUNNING
    -
    -
    -
    {stopped}
    -
    STOPPED
    -
    -
    -
    {total}
    -
    TOTAL
    -
    -
    - ); -} diff --git a/src/components/services/widgets/radarr.jsx b/src/components/services/widgets/radarr.jsx deleted file mode 100644 index 562138418..000000000 --- a/src/components/services/widgets/radarr.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import useSWR from "swr"; - -export default function Radarr({ service }) { - const config = service.widget; - - function buildApiUrl(endpoint) { - const { url, key } = config; - return `${url}/api/v3/${endpoint}?apikey=${key}`; - } - - const { data: moviesData, error: moviesError } = useSWR(buildApiUrl("movie")); - - const { data: queuedData, error: queuedError } = useSWR( - buildApiUrl("queue/status") - ); - - if (moviesError || queuedError) { - return ( -
    -
    Radarr API Error
    -
    - ); - } - - if (!moviesData || !queuedData) { - return ( -
    -
    -
    -
    -
    WANTED
    -
    -
    -
    -
    -
    QUEUED
    -
    -
    -
    -
    -
    MOVIES
    -
    -
    - ); - } - - const wanted = moviesData.filter((movie) => movie.isAvailable === false); - const have = moviesData.filter((movie) => movie.isAvailable === true); - - return ( -
    -
    -
    {wanted.length}
    -
    WANTED
    -
    -
    -
    {queuedData.totalCount}
    -
    QUEUED
    -
    -
    -
    {moviesData.length}
    -
    MOVIES
    -
    -
    - ); -} diff --git a/src/components/services/widgets/service/docker.jsx b/src/components/services/widgets/service/docker.jsx new file mode 100644 index 000000000..396398fbb --- /dev/null +++ b/src/components/services/widgets/service/docker.jsx @@ -0,0 +1,56 @@ +import useSWR from "swr"; + +import { calculateCPUPercent, formatBytes } from "utils/stats-helpers"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Docker({ service }) { + const config = service.widget; + + const { data: statusData, error: statusError } = useSWR( + `/api/docker/status/${config.container}/${config.server || ""}`, + { + refreshInterval: 1500, + } + ); + + const { data: statsData, error: statsError } = useSWR( + `/api/docker/stats/${config.container}/${config.server || ""}`, + { + refreshInterval: 1500, + } + ); + + if (statsError || statusError) { + return ; + } + + if (statusData && statusData.status !== "running") { + return ( + + + + ); + } + + if (!statsData || !statusData) { + return ( + + + + + + + ); + } + + return ( + + + + + + + ); +} diff --git a/src/components/services/widgets/service/emby.jsx b/src/components/services/widgets/service/emby.jsx new file mode 100644 index 000000000..230be6416 --- /dev/null +++ b/src/components/services/widgets/service/emby.jsx @@ -0,0 +1,45 @@ +import useSWR from "swr"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Emby({ service }) { + const config = service.widget; + + function buildApiUrl(endpoint) { + const { url, key } = config; + return `${url}/emby/${endpoint}?api_key=${key}`; + } + + const { data: sessionsData, error: sessionsError } = useSWR(buildApiUrl(`Sessions`), { + refreshInterval: 1000, + }); + + if (sessionsError) { + return ; + } + + if (!sessionsData) { + return ( + + + + + + ); + } + + const playing = sessionsData.filter((session) => session.hasOwnProperty("NowPlayingItem")); + const transcoding = sessionsData.filter( + (session) => session.hasOwnProperty("PlayState") && session.PlayState.PlayMethod === "Transcode" + ); + const bitrate = playing.reduce((acc, session) => acc + session.NowPlayingItem.Bitrate, 0); + + return ( + + + + + + ); +} diff --git a/src/components/services/widgets/service/nzbget.jsx b/src/components/services/widgets/service/nzbget.jsx new file mode 100644 index 000000000..87d77b91d --- /dev/null +++ b/src/components/services/widgets/service/nzbget.jsx @@ -0,0 +1,64 @@ +import useSWR from "swr"; +import { JSONRPCClient } from "json-rpc-2.0"; + +import { formatBytes } from "utils/stats-helpers"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Nzbget({ service }) { + const config = service.widget; + + const constructedUrl = new URL(config.url); + constructedUrl.pathname = "jsonrpc"; + + const client = new JSONRPCClient((jsonRPCRequest) => + fetch(constructedUrl.toString(), { + method: "POST", + headers: { + "content-type": "application/json", + authorization: `Basic ${btoa(`${config.username}:${config.password}`)}`, + }, + body: JSON.stringify(jsonRPCRequest), + }).then(async (response) => { + if (response.status === 200) { + const jsonRPCResponse = await response.json(); + return client.receive(jsonRPCResponse); + } else if (jsonRPCRequest.id !== undefined) { + return Promise.reject(new Error(response.statusText)); + } + }) + ); + + const { data: statusData, error: statusError } = useSWR( + "status", + (resource) => { + return client.request(resource).then((response) => response); + }, + { + refreshInterval: 1000, + } + ); + + if (statusError) { + return ; + } + + if (!statusData) { + return ( + + + + + + ); + } + + return ( + + + + + + ); +} diff --git a/src/components/services/widgets/service/ombi.jsx b/src/components/services/widgets/service/ombi.jsx new file mode 100644 index 000000000..54c44f7a3 --- /dev/null +++ b/src/components/services/widgets/service/ombi.jsx @@ -0,0 +1,49 @@ +import useSWR from "swr"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Ombi({ service }) { + const config = service.widget; + + function buildApiUrl(endpoint) { + const { url } = config; + return `${url}/api/v1/${endpoint}`; + } + + const fetcher = (url) => { + return fetch(url, { + method: "GET", + withCredentials: true, + credentials: "include", + headers: { + ApiKey: `${config.key}`, + "Content-Type": "application/json", + }, + }).then((res) => res.json()); + }; + + const { data: statsData, error: statsError } = useSWR(buildApiUrl(`Request/count`), fetcher); + + 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 new file mode 100644 index 000000000..d29ec0d06 --- /dev/null +++ b/src/components/services/widgets/service/portainer.jsx @@ -0,0 +1,59 @@ +import useSWR from "swr"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Portainer({ service }) { + const config = service.widget; + + function buildApiUrl(endpoint) { + const { url, env } = config; + const reqUrl = new URL(`/api/endpoints/${env}/${endpoint}`, url); + return `/api/proxy?url=${encodeURIComponent(reqUrl)}`; + } + + const fetcher = async (url) => { + const res = await fetch(url, { + method: "GET", + withCredentials: true, + credentials: "include", + headers: { + "X-API-Key": `${config.key}`, + "Content-Type": "application/json", + }, + }); + return await res.json(); + }; + + const { data: containersData, error: containersError } = useSWR(buildApiUrl(`docker/containers/json`), fetcher); + + 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/radarr.jsx b/src/components/services/widgets/service/radarr.jsx new file mode 100644 index 000000000..d6e42cb83 --- /dev/null +++ b/src/components/services/widgets/service/radarr.jsx @@ -0,0 +1,41 @@ +import useSWR from "swr"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Radarr({ service }) { + const config = service.widget; + + function buildApiUrl(endpoint) { + const { url, key } = config; + return `${url}/api/v3/${endpoint}?apikey=${key}`; + } + + const { data: moviesData, error: moviesError } = useSWR(buildApiUrl("movie")); + const { data: queuedData, error: queuedError } = useSWR(buildApiUrl("queue/status")); + + if (moviesError || queuedError) { + return ; + } + + if (!moviesData || !queuedData) { + return ( + + + + + + ); + } + + const wanted = moviesData.filter((movie) => movie.isAvailable === false); + const have = moviesData.filter((movie) => movie.isAvailable === true); + + return ( + + + + + + ); +} diff --git a/src/components/services/widgets/service/sonarr.jsx b/src/components/services/widgets/service/sonarr.jsx new file mode 100644 index 000000000..36afee20e --- /dev/null +++ b/src/components/services/widgets/service/sonarr.jsx @@ -0,0 +1,39 @@ +import useSWR from "swr"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Sonarr({ service }) { + const config = service.widget; + + function buildApiUrl(endpoint) { + const { url, key } = config; + return `${url}/api/v3/${endpoint}?apikey=${key}`; + } + + const { data: wantedData, error: wantedError } = useSWR(buildApiUrl("wanted/missing")); + const { data: queuedData, error: queuedError } = useSWR(buildApiUrl("queue")); + const { data: seriesData, error: seriesError } = useSWR(buildApiUrl("series")); + + if (wantedError || queuedError || seriesError) { + return ; + } + + if (!wantedData || !queuedData || !seriesData) { + return ( + + + + + + ); + } + + return ( + + + + + + ); +} diff --git a/src/components/services/widgets/sonarr.jsx b/src/components/services/widgets/sonarr.jsx deleted file mode 100644 index afdda3965..000000000 --- a/src/components/services/widgets/sonarr.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import useSWR from "swr"; - -export default function Sonarr({ service }) { - const config = service.widget; - - function buildApiUrl(endpoint) { - const { url, key } = config; - return `${url}/api/v3/${endpoint}?apikey=${key}`; - } - - const { data: wantedData, error: wantedError } = useSWR( - buildApiUrl("wanted/missing") - ); - - const { data: queuedData, error: queuedError } = useSWR(buildApiUrl("queue")); - - const { data: seriesData, error: seriesError } = useSWR( - buildApiUrl("series") - ); - - if (wantedError || queuedError || seriesError) { - return ( -
    -
    Sonarr API Error
    -
    - ); - } - - if (!wantedData || !queuedData || !seriesData) { - return ( -
    -
    Loading
    -
    - ); - } - - return ( -
    -
    -
    {wantedData.totalRecords}
    -
    WANTED
    -
    -
    -
    {queuedData.totalRecords}
    -
    QUEUED
    -
    -
    -
    {seriesData.length}
    -
    SERIES
    -
    -
    - ); -} diff --git a/src/components/services/widgets/widget.jsx b/src/components/services/widgets/widget.jsx new file mode 100644 index 000000000..98e4683eb --- /dev/null +++ b/src/components/services/widgets/widget.jsx @@ -0,0 +1,11 @@ +export default function Widget({ error = false, children }) { + if (error) { + return ( +
    +
    {error}
    +
    + ); + } + + return
    {children}
    ; +}