diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 3cdf1b45..49a85047 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -232,6 +232,10 @@
"stopped": "Stopped",
"total": "Total"
},
+ "tailscale": {
+ "private_ip": "Private IP",
+ "status": "Status"
+ },
"tdarr": {
"queue": "Queue",
"processed": "Processed",
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 93cdb995..5d4b7e3b 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 f8828f3b..c909bfe0 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 00000000..e80f9e8c
--- /dev/null
+++ b/src/widgets/tailscale/component.jsx
@@ -0,0 +1,37 @@
+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 { widget } = service;
+
+ const { data: statsData, error: statsError } = useWidgetAPI(widget, "device");
+
+ if (statsError) {
+ return ;
+ }
+
+ if (!statsData) {
+ return (
+
+
+
+
+ );
+ }
+
+ const getStatus = () => {
+ const { endpoints, latency } = statsData.clientConnectivity
+ return (endpoints.length === 0 || Object.keys(latency).length === 0) ? "Offline" : "Online"
+ }
+
+
+ const privateIP = statsData.addresses[0]
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/widgets/tailscale/widget.js b/src/widgets/tailscale/widget.js
new file mode 100644
index 00000000..7891ca80
--- /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}?fields=all",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ device: {
+ endpoint: "device"
+ },
+ },
+};
+
+export default widget;
\ No newline at end of file
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 9e155383..20f36a2b 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,