From 4c6150a545903836c30d0ff64be07dd47700fcc4 Mon Sep 17 00:00:00 2001 From: Bobby Driggs Date: Thu, 29 Aug 2024 10:51:36 -0700 Subject: [PATCH] Feature: Technitium DNS Widget (#3904) --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/index.md | 1 + docs/widgets/services/technitium.md | 26 ++++++ mkdocs.yml | 1 + public/locales/en/common.json | 13 +++ src/utils/config/service-helpers.js | 6 ++ src/widgets/components.js | 1 + src/widgets/technitium/component.jsx | 121 +++++++++++++++++++++++++++ src/widgets/technitium/widget.js | 17 ++++ src/widgets/widgets.js | 2 + 9 files changed, 188 insertions(+) create mode 100644 docs/widgets/services/technitium.md create mode 100644 src/widgets/technitium/component.jsx create mode 100644 src/widgets/technitium/widget.js diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index c39ac0f09..61d11df23 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -113,6 +113,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Syncthing Relay Server](syncthing-relay-server.md) - [Tailscale](tailscale.md) - [Tandoor](tandoor.md) +- [Technitium DNS](technitium.md) - [TDarr](tdarr.md) - [Traefik](traefik.md) - [Transmission](transmission.md) diff --git a/docs/widgets/services/technitium.md b/docs/widgets/services/technitium.md new file mode 100644 index 000000000..70f5e48f5 --- /dev/null +++ b/docs/widgets/services/technitium.md @@ -0,0 +1,26 @@ +--- +title: Technitium DNS Server +description: Technitium DNS Server Widget Configuration +--- + +Learn more about [Technitium DNS Server](https://technitium.com/dns/). + +Allowed fields (up to 4): `["totalQueries","totalNoError","totalServerFailure","totalNxDomain","totalRefused","totalAuthoritative","totalRecursive","totalCached","totalBlocked","totalDropped","totalClients"]`. + +Defaults to: `["totalQueries", "totalAuthoritative", "totalCached", "totalServerFailure"]` + +```yaml +widget: + type: technitium + url: + key: biglongapitoken + range: LastDay # optional, defaults to LastHour +``` + +#### API Key + +This can be generated via the Technitium DNS Dashboard, and should be generated from a special API specific user. + +#### Range + +`range` value determines how far back of statistics to pull data for. The value comes directly from Technitium API documentation found [here](https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md#dashboard-api-calls), defined as `"type"`. The value can be one of: `LastHour`, `LastDay`, `LastWeek`, `LastMonth`, `LastYear`. diff --git a/mkdocs.yml b/mkdocs.yml index d48cc35e0..a8096ad23 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -137,6 +137,7 @@ nav: - widgets/services/syncthing-relay-server.md - widgets/services/tailscale.md - widgets/services/tandoor.md + - widgets/services/technitium.md - widgets/services/tdarr.md - widgets/services/traefik.md - widgets/services/transmission.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index fabf5d84d..786d98f0b 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -323,6 +323,19 @@ "seconds": "{{number}}s", "ago": "{{value}} Ago" }, + "technitium": { + "totalQueries": "Queries", + "totalNoError": "Success", + "totalServerFailure": "Failures", + "totalNxDomain": "NX Domains", + "totalRefused": "Refused", + "totalAuthoritative": "Authoritative", + "totalRecursive": "Recursive", + "totalCached": "Cached", + "totalBlocked": "Blocked", + "totalDropped": "Dropped", + "totalClients": "Clients" + }, "tdarr": { "queue": "Queue", "processed": "Processed", diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 3dde943d7..e8070bc70 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -473,6 +473,9 @@ export function cleanServiceGroups(groups) { // wgeasy threshold, + + // technitium + range, } = cleanedService.widget; let fieldsList = fields; @@ -617,6 +620,9 @@ export function cleanServiceGroups(groups) { if (type === "frigate") { if (enableRecentEvents !== undefined) cleanedService.widget.enableRecentEvents = enableRecentEvents; } + if (type === "technitium") { + if (range !== undefined) cleanedService.widget.range = range; + } } return cleanedService; diff --git a/src/widgets/components.js b/src/widgets/components.js index b2d6659e3..3d800d0ed 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -112,6 +112,7 @@ const components = { tailscale: dynamic(() => import("./tailscale/component")), tandoor: dynamic(() => import("./tandoor/component")), tautulli: dynamic(() => import("./tautulli/component")), + technitium: dynamic(() => import("./technitium/component")), tdarr: dynamic(() => import("./tdarr/component")), traefik: dynamic(() => import("./traefik/component")), transmission: dynamic(() => import("./transmission/component")), diff --git a/src/widgets/technitium/component.jsx b/src/widgets/technitium/component.jsx new file mode 100644 index 000000000..76165763c --- /dev/null +++ b/src/widgets/technitium/component.jsx @@ -0,0 +1,121 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +const MAX_ALLOWED_FIELDS = 4; + +export const technitiumDefaultFields = ["totalQueries", "totalAuthoritative", "totalCached", "totalServerFailure"]; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const params = { + type: widget.range ?? "LastHour", + }; + + const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats", params); + + // Default fields + if (!widget.fields?.length > 0) { + widget.fields = technitiumDefaultFields; + } + + // Limits max number of displayed fields + if (widget.fields?.length > MAX_ALLOWED_FIELDS) { + widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS); + } + + if (statsError) { + return ; + } + + if (!statsData) { + return ( + + + + + + + + + + + + + + ); + } + + function toPercent(value, total) { + return t("common.percent", { + value: !Number.isNaN(value / total) ? value / total : 0, + maximumFractionDigits: 2, + }); + } + + return ( + + + + + + + + + + + + + + ); +} diff --git a/src/widgets/technitium/widget.js b/src/widgets/technitium/widget.js new file mode 100644 index 000000000..fb0d42bd0 --- /dev/null +++ b/src/widgets/technitium/widget.js @@ -0,0 +1,17 @@ +import genericProxyHandler from "utils/proxy/handlers/generic"; +import { asJson } from "utils/proxy/api-helpers"; + +const widget = { + api: "{url}/api/{endpoint}?token={key}&utc=true", + proxyHandler: genericProxyHandler, + mappings: { + stats: { + endpoint: "dashboard/stats/get", + validate: ["response", "status"], + params: ["type"], + map: (data) => asJson(data).response.stats, + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index f4e55d57c..ed468e889 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -103,6 +103,7 @@ import swagdashboard from "./swagdashboard/widget"; import tailscale from "./tailscale/widget"; import tandoor from "./tandoor/widget"; import tautulli from "./tautulli/widget"; +import technitium from "./technitium/widget"; import tdarr from "./tdarr/widget"; import traefik from "./traefik/widget"; import transmission from "./transmission/widget"; @@ -228,6 +229,7 @@ const widgets = { tailscale, tandoor, tautulli, + technitium, tdarr, traefik, transmission,