44
55A widget for Glance that displays all devices in your Tailscale tailnet, showing their connection status, update availability, and IP addresses.
66
7- ## Option 1: API Key (Recommended)
7+ ## Setup
88
9- The standard implementation using Tailscale API keys. Simple to set up but requires manual api key renewal every 90 days maximum.
9+ ### Option 1: API Key (Recommended)
1010
11- ### Setup
11+ The standard implementation using Tailscale API keys. Simple to set up but requires manual API key renewal every 90 days maximum.
1212
13131 . ** Generate API Key:**
1414 - Go to [ Tailscale admin console] ( https://login.tailscale.com/admin/settings/keys )
@@ -18,169 +18,49 @@ The standard implementation using Tailscale API keys. Simple to set up but requi
18182 . ** Configure Widget:**
1919 - Set the ` TAILSCALE_API_KEY ` environment variable to your generated key
2020
21- ``` yaml
22- - type : custom-api
23- title : Tailscale Devices
24- title-url : https://login.tailscale.com/admin/machines
25- url : https://api.tailscale.com/api/v2/tailnet/-/devices
26- headers :
27- Authorization : Bearer ${TAILSCALE_API_KEY}
28- cache : 10m
29- template : |
30- {{/* User Variables */}}
31- {{/* Set to true if you'd like an indicator for online devices */}}
32- {{ $enableOnlineIndicator := false }}
33-
34- <style>
35- .device-info-container {
36- position: relative;
37- overflow: hidden;
38- height: 1.5em;
39- }
40-
41- .device-info {
42- display: flex;
43- transition: transform 0.2s ease, opacity 0.2s ease;
44- }
45-
46- .device-ip {
47- position: absolute;
48- top: 0;
49- left: 0;
50- transform: translateY(-100%);
51- opacity: 0;
52- transition: transform 0.2s ease, opacity 0.2s ease;
53- }
54-
55- .device-info-container:hover .device-info {
56- transform: translateY(100%);
57- opacity: 0;
58- }
59-
60- .device-info-container:hover .device-ip {
61- transform: translateY(0);
62- opacity: 1;
63- }
64-
65- .update-indicator {
66- width: 8px;
67- height: 8px;
68- border-radius: 50%;
69- background-color: var(--color-primary);
70- display: inline-block;
71- margin-left: 4px;
72- vertical-align: middle;
73- }
74-
75- .offline-indicator {
76- width: 8px;
77- height: 8px;
78- border-radius: 50%;
79- background-color: var(--color-negative);
80- display: inline-block;
81- margin-left: 4px;
82- vertical-align: middle;
83- }
84-
85- .online-indicator {
86- width: 8px;
87- height: 8px;
88- border-radius: 50%;
89- background-color: var(--color-positive);
90- display: inline-block;
91- margin-left: 4px;
92- vertical-align: middle;
93- }
94-
95- .device-name-container {
96- display: flex;
97- align-items: center;
98- gap: 8px;
99- }
100-
101- .indicators-container {
102- display: flex;
103- align-items: center;
104- gap: 4px;
105- }
106- </style>
107- <ul class="list list-gap-10 collapsible-container" data-collapse-after="4">
108- {{ range .JSON.Array "devices" }}
109- <li>
110- <div class="flex items-center gap-10">
111- <div class="device-name-container grow">
112- <span class="size-h4 block text-truncate color-primary">
113- {{ findMatch "^([^.]+)" (.String "name") }}
114- </span>
115- <div class="indicators-container">
116- {{ if (.Bool "updateAvailable") }}
117- <span class="update-indicator" data-popover-type="text" data-popover-text="Update Available"></span>
118- {{ end }}
119-
120- {{ $lastSeen := .String "lastSeen" | parseTime "rfc3339" }}
121- {{ if not ($lastSeen.After (offsetNow "-10s")) }}
122- {{ $lastSeenTimezoned := $lastSeen.In now.Location }}
123- <span class="offline-indicator" data-popover-type="text"
124- data-popover-text="Offline - Last seen {{ $lastSeenTimezoned.Format " Jan 2 3:04pm" }}"></span>
125- {{ else if $enableOnlineIndicator }}
126- <span class="online-indicator" data-popover-type="text" data-popover-text="Online"></span>
127- {{ end }}
128- </div>
129- </div>
130- </div>
131- <div class="device-info-container">
132- <ul class="list-horizontal-text device-info">
133- <li>{{ .String "os" }}</li>
134- <li>{{ .String "user" }}</li>
135- </ul>
136- <div class="device-ip">
137- {{ .String "addresses.0"}}
138- </div>
139- </div>
140- </li>
141- {{ end }}
142- </ul>
143- ` ` `
144-
145- ## Option 2: OAuth Proxy (Alternative)
21+ ### Option 2: OAuth Proxy (Alternative)
14622
14723An alternative implementation that uses automatically refreshing OAuth tokens, eliminating the need for manual key renewal. Created by @5at0ri , requires [ 5at0ri's OAuth Proxy] ( https://github.com/5at0ri/tailscale-token-manager ) .
14824
149- ### Setup
150-
151251 . ** Deploy OAuth Proxy:**
15226 - Set up OAuth client credentials in your [ Tailscale admin console] ( https://login.tailscale.com/admin/settings/oauth )
15327 - Deploy the [ OAuth Proxy container] ( https://github.com/5at0ri/tailscale-token-manager )
15428 - Configure it with your OAuth Client ID and Client Secret
15529
156302 . ** Configure Widget:**
157- - Point the ` url` to your OAuth proxy container endpoint
158- - The proxy handles all authentication automatically
31+ - Replace the ` url ` line with: ` url: http://tailscale-token-manager:1180/devices `
32+ - Remove the ` headers: ` section entirely
33+ - Change ` cache: ` to ` 10s ` for more frequent updates
34+
35+ ## Template
15936
16037``` yaml
16138- type : custom-api
16239 title : Tailscale Devices
16340 title-url : https://login.tailscale.com/admin/machines
164- url: http://tailscale-token-manager:1180/devices # URL to your OAuth proxy container
165- cache: 10s
41+ url : https://api.tailscale.com/api/v2/tailnet/-/devices
42+ headers :
43+ Authorization : Bearer ${TAILSCALE_API_KEY}
44+ cache : 10m
45+ options :
46+ # collapseAfter: 4
47+ # disableOfflineIndicator: true
48+ # disableUpdateIndicator: true
49+ # prioritiseTags: true
16650 template : |
167- {{/* User Variables */}}
168- {{/* Set to true if you'd like an indicator for online devices */}}
169- {{ $enableOnlineIndicator := false }}
170-
17151 <style>
172- .device-info-container {
52+ .device-info-container-tailscale {
17353 position: relative;
17454 overflow: hidden;
17555 height: 1.5em;
17656 }
17757
178- .device-info {
58+ .device-info-tailscale {
17959 display: flex;
18060 transition: transform 0.2s ease, opacity 0.2s ease;
18161 }
18262
183- .device-ip {
63+ .device-ip-tailscale {
18464 position: absolute;
18565 top: 0;
18666 left: 0;
@@ -189,17 +69,17 @@ An alternative implementation that uses automatically refreshing OAuth tokens, e
18969 transition: transform 0.2s ease, opacity 0.2s ease;
19070 }
19171
192- .device-info-container:hover .device-info {
72+ .device-info-container-tailscale :hover .device-info-tailscale {
19373 transform: translateY(100%);
19474 opacity: 0;
19575 }
19676
197- .device-info-container:hover .device-ip {
77+ .device-info-container-tailscale :hover .device-ip-tailscale {
19878 transform: translateY(0);
19979 opacity: 1;
20080 }
20181
202- .update-indicator {
82+ .update-indicator-tailscale {
20383 width: 8px;
20484 height: 8px;
20585 border-radius: 50%;
@@ -209,7 +89,7 @@ An alternative implementation that uses automatically refreshing OAuth tokens, e
20989 vertical-align: middle;
21090 }
21191
212- .offline-indicator {
92+ .offline-indicator-tailscale {
21393 width: 8px;
21494 height: 8px;
21595 border-radius: 50%;
@@ -219,62 +99,69 @@ An alternative implementation that uses automatically refreshing OAuth tokens, e
21999 vertical-align: middle;
220100 }
221101
222- .online-indicator {
223- width: 8px;
224- height: 8px;
225- border-radius: 50%;
226- background-color: var(--color-positive);
227- display: inline-block;
228- margin-left: 4px;
229- vertical-align: middle;
230- }
231-
232- .device-name-container {
102+ .device-name-container-tailscale {
233103 display: flex;
234104 align-items: center;
235105 gap: 8px;
236106 }
237107
238- .indicators-container {
108+ .indicators-container-tailscale {
239109 display: flex;
240110 align-items: center;
241111 gap: 4px;
242112 }
243113 </style>
244- <ul class="list list-gap-10 collapsible-container" data-collapse-after="4 ">
114+ <ul class="list list-gap-10 collapsible-container" data-collapse-after="{{ .Options.IntOr "collapseAfter" 3 }} ">
245115 {{ range .JSON.Array "devices" }}
246116 <li>
247117 <div class="flex items-center gap-10">
248- <div class="device-name-container grow">
118+ <div class="device-name-container-tailscale grow">
249119 <span class="size-h4 block text-truncate color-primary">
250120 {{ findMatch "^([^.]+)" (.String "name") }}
251121 </span>
252- <div class="indicators-container">
253- {{ if (.Bool "updateAvailable") }}
254- <span class="update-indicator" data-popover-type="text" data-popover-text="Update Available"></span>
122+ <div class="indicators-container-tailscale ">
123+ {{ if and (not ($.Options.BoolOr "disableUpdateIndicator" false)) (.Bool "updateAvailable") }}
124+ <span class="update-indicator-tailscale " data-popover-type="text" data-popover-text="Update Available"></span>
255125 {{ end }}
256126
127+ {{ if not ($.Options.BoolOr "disableOfflineIndicator" false) }}
257128 {{ $lastSeen := .String "lastSeen" | parseTime "rfc3339" }}
258129 {{ if not ($lastSeen.After (offsetNow "-10s")) }}
259130 {{ $lastSeenTimezoned := $lastSeen.In now.Location }}
260- <span class="offline-indicator" data-popover-type="text"
131+ <span class="offline-indicator-tailscale " data-popover-type="text"
261132 data-popover-text="Offline - Last seen {{ $lastSeenTimezoned.Format " Jan 2 3:04pm" }}"></span>
262- {{ else if $enableOnlineIndicator }}
263- <span class="online-indicator" data-popover-type="text" data-popover-text="Online"></span>
264133 {{ end }}
134+ {{ end }}
135+
265136 </div>
266137 </div>
267138 </div>
268- <div class="device-info-container">
269- <ul class="list-horizontal-text device-info">
139+ <div class="device-info-container-tailscale ">
140+ <ul class="list-horizontal-text device-info-tailscale ">
270141 <li>{{ .String "os" }}</li>
271- <li>{{ .String "user" }}</li>
142+ <li>
143+ {{ if and ($.Options.BoolOr "prioritiseTags" false) (.Exists "tags.0") }}
144+ {{ trimPrefix "tag:" (.String "tags.0") }}
145+ {{ else }}
146+ {{ .String "user" }}
147+ {{ end }}
148+ </li>
272149 </ul>
273- <div class="device-ip">
150+ <div class="device-ip-tailscale ">
274151 {{ .String "addresses.0"}}
275152 </div>
276153 </div>
277154 </li>
278155 {{ end }}
279156 </ul>
280157` ` `
158+
159+ ## Available Options
160+ *All options have default values and are not required for the widget to function.*
161+
162+ | Option | Type | Default | Description |
163+ | ------------------------- | ------- | ------- | -------------------------------------------------------------------------------------------- |
164+ | ` collapseAfter` | integer | `3` | Number of devices to show before collapsing the list. |
165+ | `disableOfflineIndicator` | boolean | `false` | When set to `true`, hides the red dot indicator for offline devices. |
166+ | `disableUpdateIndicator` | boolean | `false` | When set to `true`, hides the blue dot indicator when updates are available. |
167+ | `prioritiseTags` | boolean | `false` | When set to `true`, displays the device's primary tag instead of the user (if a tag exists). |
0 commit comments