Add AdGuard, Bazarr, and Coin Market Cap widgets

- Allow setting HTTP method in widget.js
- Allow sending allow listed query params to proxy
This commit is contained in:
Jason Fischer 2022-09-25 14:31:41 -07:00
parent f999f4a467
commit 03fa2f86d7
No known key found for this signature in database
10 changed files with 251 additions and 9 deletions

View file

@ -0,0 +1,44 @@
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: adguardData, error: adguardError } = useSWR(formatProxyUrl(config, "stats"));
if (adguardError) {
return <Widget error={t("widget.api_error")} />;
}
if (!adguardData) {
return (
<Widget>
<Block label={t("adguard.queries")} />
<Block label={t("adguard.blocked")} />
<Block label={t("adguard.filtered")} />
<Block label={t("adguard.latency")} />
</Widget>
);
}
const filtered =
adguardData.num_replaced_safebrowsing + adguardData.num_replaced_safesearch + adguardData.num_replaced_parental;
return (
<Widget>
<Block label={t("adguard.queries")} value={t("common.number", { value: adguardData.num_dns_queries })} />
<Block label={t("adguard.blocked")} value={t("common.number", { value: adguardData.num_blocked_filtering })} />
<Block label={t("adguard.filtered")} value={t("common.number", { value: filtered })} />
<Block
label={t("adguard.latency")}
value={t("common.ms", { value: adguardData.avg_processing_time * 1000, style: "unit", unit: "millisecond" })}
/>
</Widget>
);
}

View file

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

View file

@ -0,0 +1,35 @@
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: episodesData, error: episodesError } = useSWR(formatProxyUrl(config, "episodes"));
const { data: moviesData, error: moviesError } = useSWR(formatProxyUrl(config, "movies"));
if (episodesError || moviesError) {
return <Widget error={t("widget.api_error")} />;
}
if (!episodesData || !moviesData) {
return (
<Widget>
<Block label={t("bazarr.missingEpisodes")} />
<Block label={t("bazarr.missingMovies")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("bazarr.missingEpisodes")} value={t("common.number", { value: episodesData.total })} />
<Block label={t("bazarr.missingMovies")} value={t("common.number", { value: moviesData.total })} />
</Widget>
);
}

View file

@ -0,0 +1,24 @@
import genericProxyHandler from "utils/proxies/generic";
import { asJson } from "utils/api-helpers";
const widget = {
api: "{url}/api/{endpoint}/wanted?apikey={key}",
proxyHandler: genericProxyHandler,
mappings: {
"movies": {
endpoint: "movies",
map: (data) => ({
total: asJson(data).total,
}),
},
"episodes": {
endpoint: "episodes",
map: (data) => ({
total: asJson(data).total,
}),
},
},
};
export default widget;

View file

@ -0,0 +1,92 @@
import useSWR from "swr";
import { useState } from "react";
import { useTranslation } from "next-i18next";
import classNames from "classnames";
import Widget from "components/services/widgets/widget";
import Block from "components/services/widgets/block";
import Dropdown from "components/services/dropdown";
import { formatProxyUrl } from "utils/api-helpers";
export default function Component({ 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 (
<Widget>
<Block value={t("coinmarketcap.configure")} />
</Widget>
);
}
if (statsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData || !dateRange) {
return (
<Widget>
<Block value={t("coinmarketcap.configure")} />
</Widget>
);
}
const { data } = statsData;
return (
<Widget>
<div className={classNames(service.description ? "-top-10" : "-top-8", "absolute right-1")}>
<Dropdown options={dateRangeOptions} value={dateRange} setValue={setDateRange} />
</div>
<div className="flex flex-col w-full">
{symbols.map((symbol) => (
<div
key={data[symbol].symbol}
className="bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-row items-center justify-between p-1 text-xs"
>
<div className="font-thin pl-2">{data[symbol].name}</div>
<div className="flex flex-row text-right">
<div className="font-bold mr-2">
{t("common.number", {
value: data[symbol].quote[currencyCode].price,
style: "currency",
currency: currencyCode,
})}
</div>
<div
className={`font-bold w-10 mr-2 ${
data[symbol].quote[currencyCode][`percent_change_${dateRange}`] > 0
? "text-emerald-300"
: "text-rose-300"
}`}
>
{data[symbol].quote[currencyCode][`percent_change_${dateRange}`].toFixed(2)}%
</div>
</div>
</div>
))}
</div>
</Widget>
);
}

View file

@ -0,0 +1,15 @@
import credentialedProxyHandler from "utils/proxies/credentialed";
const widget = {
api: "https://pro-api.coinmarketcap.com/{endpoint}",
proxyHandler: credentialedProxyHandler,
mappings: {
"v1/cryptocurrency/quotes/latest": {
endpoint: "v1/cryptocurrency/quotes/latest",
params: ["symbol", "convert"],
},
},
};
export default widget;

View file

@ -1,6 +1,9 @@
import dynamic from "next/dynamic";
const components = {
adguard: dynamic(() => import("./adguard/component")),
bazarr: dynamic(() => import("./bazarr/component")),
coinmarketcap: dynamic(() => import("./coinmarketcap/component")),
overseerr: dynamic(() => import("./overseerr/component")),
radarr: dynamic(() => import("./radarr/component")),
sonarr: dynamic(() => import("./sonarr/component")),

View file

@ -1,8 +1,14 @@
import adguard from "./adguard/widget";
import bazarr from "./bazarr/widget";
import coinmarketcap from "./coinmarketcap/widget";
import overseerr from "./overseerr/widget";
import radarr from "./radarr/widget";
import sonarr from "./sonarr/widget"
const widgets = {
adguard,
bazarr,
coinmarketcap,
overseerr,
radarr,
sonarr,