From acc19ccca17020715f0cfa9deac25700db4dc92f Mon Sep 17 00:00:00 2001 From: Andre Date: Sun, 7 May 2023 00:09:33 -0400 Subject: [PATCH] Add Tailscale Widget (#1468) * Added tailscale widget * finished tailscale widget * Consolidated date comparison to it's own function * Modified to follow Airbnb's style guide * Removed refresh and added translations * fix some tailscale translation strings --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- public/locales/en/common.json | 14 +++++ src/utils/proxy/handlers/credentialed.js | 1 + src/widgets/components.js | 1 + src/widgets/tailscale/component.jsx | 72 ++++++++++++++++++++++++ src/widgets/tailscale/widget.js | 14 +++++ src/widgets/widgets.js | 2 + 6 files changed, 104 insertions(+) create mode 100644 src/widgets/tailscale/component.jsx create mode 100644 src/widgets/tailscale/widget.js diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 3cdf1b458..4c0c61a30 100755 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -232,6 +232,20 @@ "stopped": "Stopped", "total": "Total" }, + "tailscale": { + "address": "Address", + "expires": "Expires", + "never": "Never", + "last_seen": "Last Seen", + "now": "Now", + "years": "{{number}}y", + "weeks": "{{number}}w", + "days": "{{number}}d", + "hours": "{{number}}h", + "minutes": "{{number}}m", + "seconds": "{{number}}s", + "ago": "{{value}} Ago" + }, "tdarr": { "queue": "Queue", "processed": "Processed", diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 93cdb9959..5d4b7e3b8 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -32,6 +32,7 @@ export default async function credentialedProxyHandler(req, res, map) { "authentik", "cloudflared", "ghostfolio", + "tailscale", "truenas", "pterodactyl", ].includes(widget.type)) diff --git a/src/widgets/components.js b/src/widgets/components.js index f8828f3bd..c909bfe00 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -71,6 +71,7 @@ const components = { sonarr: dynamic(() => import("./sonarr/component")), speedtest: dynamic(() => import("./speedtest/component")), strelaysrv: dynamic(() => import("./strelaysrv/component")), + tailscale: dynamic(() => import("./tailscale/component")), tautulli: dynamic(() => import("./tautulli/component")), tdarr: dynamic(() => import("./tdarr/component")), traefik: dynamic(() => import("./traefik/component")), diff --git a/src/widgets/tailscale/component.jsx b/src/widgets/tailscale/component.jsx new file mode 100644 index 000000000..3929b2ed1 --- /dev/null +++ b/src/widgets/tailscale/component.jsx @@ -0,0 +1,72 @@ +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"; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const { data: statsData, error: statsError } = useWidgetAPI(widget, "device"); + + if (statsError) { + return ; + } + + if (!statsData) { + return ( + + + + + + ); + } + + const { + addresses: [address], + keyExpiryDisabled, + lastSeen, + expires, + } = statsData; + + const now = new Date(); + const compareDifferenceInTwoDates = (priorDate, futureDate) => { + const diff = futureDate.getTime() - priorDate.getTime(); + const diffInYears = Math.ceil(diff / (1000 * 60 * 60 * 24 * 365)); + if (diffInYears > 1) return t("tailscale.years", { number: diffInYears }); + const diffInWeeks = Math.ceil(diff / (1000 * 60 * 60 * 24 * 7)); + if (diffInWeeks > 1) return t("tailscale.weeks", { number: diffInWeeks }); + const diffInDays = Math.ceil(diff / (1000 * 60 * 60 * 24)); + if (diffInDays > 1) return t("tailscale.days", { number: diffInDays }); + const diffInHours = Math.ceil(diff / (1000 * 60 * 60)); + if (diffInHours > 1) return t("tailscale.hours", { number: diffInHours }); + const diffInMinutes = Math.ceil(diff / (1000 * 60)); + if (diffInMinutes > 1) return t("tailscale.minutes", { number: diffInMinutes }); + const diffInSeconds = Math.ceil(diff / 1000); + if (diffInSeconds > 10) return t("tailscale.seconds", { number: diffInSeconds }); + return "Now"; + }; + + const getLastSeen = () => { + const date = new Date(lastSeen); + const diff = compareDifferenceInTwoDates(date, now); + return diff === "Now" ? t("tailscale.now") : t("tailscale.ago", { value: diff }); + }; + + const getExpiry = () => { + if (keyExpiryDisabled) return t("tailscale.never"); + const date = new Date(expires); + return compareDifferenceInTwoDates(now, date); + }; + + return ( + + + + + + ); +} diff --git a/src/widgets/tailscale/widget.js b/src/widgets/tailscale/widget.js new file mode 100644 index 000000000..a6d9e8647 --- /dev/null +++ b/src/widgets/tailscale/widget.js @@ -0,0 +1,14 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "https://api.tailscale.com/api/v2/{endpoint}/{deviceid}", + proxyHandler: credentialedProxyHandler, + + mappings: { + device: { + endpoint: "device", + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 9e1553831..20f36a2b6 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -65,6 +65,7 @@ import scrutiny from "./scrutiny/widget"; import sonarr from "./sonarr/widget"; import speedtest from "./speedtest/widget"; import strelaysrv from "./strelaysrv/widget"; +import tailscale from "./tailscale/widget"; import tautulli from "./tautulli/widget"; import tdarr from "./tdarr/widget"; import traefik from "./traefik/widget"; @@ -147,6 +148,7 @@ const widgets = { sonarr, speedtest, strelaysrv, + tailscale, tautulli, tdarr, traefik,