refactor widget api design

this passes all widget API calls through the backend, with a pluggable design and reusable API handlers
This commit is contained in:
Ben Phelps 2022-09-04 21:58:42 +03:00
parent 975f79f6cc
commit 97bf174b78
27 changed files with 370 additions and 252 deletions

View file

@ -1,24 +1,24 @@
import useSWR from "swr";
import { calculateCPUPercent, formatBytes } from "utils/stats-helpers";
import Widget from "../widget";
import Block from "../block";
import { calculateCPUPercent, formatBytes } from "utils/stats-helpers";
export default function Docker({ service }) {
const config = service.widget;
const { data: statusData, error: statusError } = useSWR(
`/api/docker/status/${config.container}/${config.server || ""}`,
{
refreshInterval: 1500,
refreshInterval: 5000,
}
);
const { data: statsData, error: statsError } = useSWR(
`/api/docker/stats/${config.container}/${config.server || ""}`,
{
refreshInterval: 1500,
refreshInterval: 5000,
}
);

View file

@ -3,17 +3,12 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Emby({ service, title = "Emby" }) {
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,
});
const { data: sessionsData, error: sessionsError } = useSWR(formatApiUrl(config, "Sessions"));
if (sessionsError) {
return <Widget error={`${title} API Error`} />;

View file

@ -3,49 +3,32 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Jellyseerr({ service }) {
const config = service.widget;
const config = service.widget;
function buildApiUrl(endpoint) {
const { url } = config;
const reqUrl = new URL(`/api/v1/${endpoint}`, url);
return `/api/proxy?url=${encodeURIComponent(reqUrl)}`;
}
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `request/count`));
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: statsData, error: statsError } = useSWR(buildApiUrl(`request/count`), fetcher);
if (statsError) {
return <Widget error="Jellyseerr API Error" />;
}
if (!statsData) {
return (
<Widget>
<Block label="Pending" />
<Block label="Approved" />
<Block label="Available" />
</Widget>
);
}
if (statsError) {
return <Widget error="Jellyseerr API Error" />;
}
if (!statsData) {
return (
<Widget>
<Block label="Pending" value={statsData.pending} />
<Block label="Approved" value={statsData.approved} />
<Block label="Available" value={statsData.available} />
</Widget>
<Widget>
<Block label="Pending" />
<Block label="Approved" />
<Block label="Available" />
</Widget>
);
}
return (
<Widget>
<Block label="Pending" value={statsData.pending} />
<Block label="Approved" value={statsData.approved} />
<Block label="Available" value={statsData.available} />
</Widget>
);
}

View file

@ -3,39 +3,12 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Npm({ service }) {
const config = service.widget;
const { url } = config;
const fetcher = async (reqUrl) => {
const { url, username, password } = config;
const loginUrl = `${url}/api/tokens`;
const body = { identity: username, secret: password };
const res = await fetch(loginUrl, {
method: "POST",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then(
async (data) =>
await fetch(reqUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + data.token,
},
})
);
return res.json();
};
const { data: infoData, error: infoError } = useSWR(`${url}/api/nginx/proxy-hosts`, fetcher);
console.log(infoData);
const { data: infoData, error: infoError } = useSWR(formatApiUrl(config, "nginx/proxy-hosts"));
if (infoError) {
return <Widget error="NGINX Proxy Manager API Error" />;

View file

@ -1,44 +1,15 @@
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";
import { formatApiUrl } from "utils/api-helpers";
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,
}
);
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config, "status"));
if (statusError) {
return <Widget error="Nzbget API Error" />;

View file

@ -3,27 +3,12 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
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);
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `Request/count`));
if (statsError) {
return <Widget error="Ombi API Error" />;

View file

@ -3,21 +3,12 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Pihole({ service }) {
const config = service.widget;
function buildApiUrl(endpoint) {
const { url, proxy } = config;
if (proxy) {
const fullUrl = `${url}/admin/${endpoint}`;
return "/api/proxy?url=" + encodeURIComponent(fullUrl);
}
return `${url}/admin/${endpoint}`;
}
const { data: piholeData, error: piholeError } = useSWR(buildApiUrl("api.php"));
const { data: piholeData, error: piholeError } = useSWR(formatApiUrl(config, "api.php"));
if (piholeError) {
return <Widget error="PiHole API Error" />;

View file

@ -3,29 +3,12 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
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?all=1`), fetcher);
const { data: containersData, error: containersError } = useSWR(formatApiUrl(config, `docker/containers/json?all=1`));
if (containersError) {
return <Widget error="Portainer API Error" />;

View file

@ -3,16 +3,13 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
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"));
const { data: moviesData, error: moviesError } = useSWR(formatApiUrl(config, "movie"));
const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue/status"));
if (moviesError || queuedError) {
return <Widget error="Radarr API Error" />;

View file

@ -1,32 +1,15 @@
import useSWR from "swr";
import RuTorrent from "rutorrent-promise";
import { formatBytes } from "utils/stats-helpers";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
import { formatBytes } from "utils/stats-helpers";
export default function Rutorrent({ service }) {
const config = service.widget;
function buildApiUrl() {
const { url, username, password } = config;
const options = {
url: `${url}/plugins/httprpc/action.php`,
};
if (username && password) {
options.username = username;
options.password = password;
}
const params = new URLSearchParams(options);
return `/api/widgets/rutorrent?${params.toString()}`;
}
const { data: statusData, error: statusError } = useSWR(buildApiUrl());
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config));
if (statusError) {
return <Widget error="Nzbget API Error" />;

View file

@ -3,17 +3,14 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
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"));
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"));
if (wantedError || queuedError || seriesError) {
return <Widget error="Sonar API Error" />;

View file

@ -4,16 +4,12 @@ import Widget from "../widget";
import Block from "../block";
import { formatBits } from "utils/stats-helpers";
import { formatApiUrl } from "utils/api-helpers";
export default function Speedtest({ service }) {
const config = service.widget;
function buildApiUrl(endpoint) {
const { url } = config;
return `${url}/api/${endpoint}`;
}
const { data: speedtestData, error: speedtestError } = useSWR(buildApiUrl("speedtest/latest"));
const { data: speedtestData, error: speedtestError } = useSWR(formatApiUrl(config, "speedtest/latest"));
if (speedtestError) {
return <Widget error="Speedtest API Error" />;
@ -31,8 +27,8 @@ export default function Speedtest({ service }) {
return (
<Widget>
<Block label="Download" value={`${formatBits(speedtestData.data.download * 1024 * 1024)}ps`} />
<Block label="Upload" value={`${formatBits(speedtestData.data.upload * 1024 * 1024)}ps`} />
<Block label="Download" value={`${formatBits(speedtestData.data.download * 1024 * 1024, 0)}ps`} />
<Block label="Upload" value={`${formatBits(speedtestData.data.upload * 1024 * 1024, 0)}ps`} />
<Block label="Ping" value={`${speedtestData.data.ping} ms`} />
</Widget>
);

View file

@ -3,18 +3,12 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Tautulli({ service }) {
const config = service.widget;
function buildApiUrl(endpoint) {
const { url, key } = config;
const fullUrl = `${url}/api/v2?apikey=${key}&cmd=${endpoint}`;
return "/api/proxy?url=" + encodeURIComponent(fullUrl);
}
const { data: statsData, error: statsError } = useSWR(buildApiUrl("get_activity"), {
refreshInterval: 1000,
});
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, "get_activity"));
if (statsError) {
return <Widget error="Tautulli API Error" />;

View file

@ -3,16 +3,12 @@ import useSWR from "swr";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Traefik({ service }) {
const config = service.widget;
function buildApiUrl(endpoint) {
const { url } = config;
const fullUrl = `${url}/api/${endpoint}`;
return `/api/proxy?url=${encodeURIComponent(fullUrl)}`;
}
const { data: traefikData, error: traefikError } = useSWR(buildApiUrl("overview"));
const { data: traefikData, error: traefikError } = useSWR(formatApiUrl(config, "overview"));
if (traefikError) {
return <Widget error="Traefik API Error" />;