<!doctype html> <html class="bg-gray-50"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQflAx4QGA4EvmzDAAAA30lEQVRIx2NgGAWMCKa8JKM4A8Ovt88ekyLCDGOoyDBJMjExMbFy8zF8/EKsCAMDE8yAPyIwFps48SJIBpAL4AZwvoSx/r0lXgQpDN58EWL5x/7/H+vL20+JFxluQKVe5b3Ke5V+0kQQCamfoYKBg4GDwUKI8d0BYkWQkrLKewYBKPPDHUFiRaiZkBgmwhj/F5IgggyUJ6i8V3mv0kCayDAAeEsklXqGAgYGhgV3CnGrwVciYSYk0kokhgS44/JxqqFpiYSZbEgskd4dEBRk1GD4wdB5twKXmlHAwMDAAACdEZau06NQUwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wNy0xNVQxNTo1Mzo0MCswMDowMCVXsDIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDctMTVUMTU6NTM6NDArMDA6MDBUCgiOAAAAAElFTkSuQmCC" /> <title>Tailscale</title> <style>{{template "web.css"}}</style> </head> <body class="py-14"> <main class="container max-w-lg mx-auto mb-8 py-6 px-8 bg-white rounded-md shadow-2xl" style="width: 95%"> <header class="flex justify-between items-center min-width-0 py-2 mb-8"> <svg width="26" height="26" viewBox="0 0 23 23" title="Tailscale" fill="none" xmlns="http://www.w3.org/2000/svg" class="flex-shrink-0 mr-4"> <circle opacity="0.2" cx="3.4" cy="3.25" r="2.7" fill="currentColor"></circle> <circle cx="3.4" cy="11.3" r="2.7" fill="currentColor"></circle> <circle opacity="0.2" cx="3.4" cy="19.5" r="2.7" fill="currentColor"></circle> <circle cx="11.5" cy="11.3" r="2.7" fill="currentColor"></circle> <circle cx="11.5" cy="19.5" r="2.7" fill="currentColor"></circle> <circle opacity="0.2" cx="11.5" cy="3.25" r="2.7" fill="currentColor"></circle> <circle opacity="0.2" cx="19.5" cy="3.25" r="2.7" fill="currentColor"></circle> <circle cx="19.5" cy="11.3" r="2.7" fill="currentColor"></circle> <circle opacity="0.2" cx="19.5" cy="19.5" r="2.7" fill="currentColor"></circle> </svg> <div class="flex items-center justify-end space-x-2 w-2/3"> {{ with .Profile }} <div class="text-right w-full leading-4"> <h4 class="truncate leading-normal">{{.LoginName}}</h4> <div class="text-xs text-gray-500 text-right"> <a href="#" class="hover:text-gray-700 js-loginButton">Switch account</a> | <a href="#" class="hover:text-gray-700 js-loginButton">Reauthenticate</a> | <a href="#" class="hover:text-gray-700 js-logoutButton">Logout</a> </div> </div> {{ end }} <div class="relative flex-shrink-0 w-8 h-8 rounded-full overflow-hidden"> {{ with .Profile.ProfilePicURL }} <div class="w-8 h-8 flex pointer-events-none rounded-full bg-gray-200" style="background-image: url('{{.}}'); background-size: cover;"></div> {{ else }} <div class="w-8 h-8 flex pointer-events-none rounded-full border border-gray-400 border-dashed"></div> {{ end }} </div> </div> </header> {{ if .IP }} <div class="border border-gray-200 bg-gray-0 rounded-md p-2 pl-3 pr-3 width-full flex items-center justify-between"> <div class="flex items-center min-width-0"> <svg class="flex-shrink-0 text-gray-600 mr-3 ml-1" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect> <rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect> <line x1="6" y1="6" x2="6.01" y2="6"></line> <line x1="6" y1="18" x2="6.01" y2="18"></line> </svg> <div> <h4 class="font-semibold truncate mr-2">{{.DeviceName}}</h4> </div> </div> <h5>{{.IP}}</h5> </div> <p class="mt-1 ml-1 mb-6 text-xs text-gray-600"> Debug info: Tailscale {{ .IPNVersion }}, tun={{.TUNMode}}{{ if .IsSynology }}, DSM{{ .DSMVersion}} {{if not .TUNMode}} (<a href="https://tailscale.com/kb/1152/synology-outbound/" class="link-underline text-gray-600" target="_blank" aria-label="Configure outbound synology traffic" rel="noopener noreferrer">outgoing access not configured</a>) {{end}} {{end}} </p> {{ end }} {{ if or (eq .Status "NeedsLogin") (eq .Status "NoState") }} {{ if .IP }} <div class="mb-6"> <p class="text-gray-700">Your device's key has expired. Reauthenticate this device by logging in again, or <a href="https://tailscale.com/kb/1028/key-expiry" class="link" target="_blank">learn more</a>.</p> </div> <a href="#" class="mb-4 js-loginButton" target="_blank"> <button class="button button-blue w-full">Reauthenticate</button> </a> {{ else }} <div class="mb-6"> <h3 class="text-3xl font-semibold mb-3">Log in</h3> <p class="text-gray-700">Get started by logging in to your Tailscale network. Or, learn more at <a href="https://tailscale.com/" class="link" target="_blank">tailscale.com</a>.</p> </div> <a href="#" class="mb-4 js-loginButton" target="_blank"> <button class="button button-blue w-full">Log In</button> </a> {{ end }} {{ else if eq .Status "NeedsMachineAuth" }} <div class="mb-4"> This device is authorized, but needs approval from a network admin before it can connect to the network. </div> {{ else }} <div class="mb-4"> <p>You are connected! Access this device over Tailscale using the device name or IP address above.</p> </div> <div class="mb-4"> <a href="#" class="mb-4 js-advertiseExitNode"> {{if .AdvertiseExitNode}} <button class="button button-red button-medium" id="enabled">Stop advertising Exit Node</button> {{else}} <button class="button button-blue button-medium" id="enabled">Advertise as Exit Node</button> {{end}} </a> </div> {{ end }} </main> <footer class="container max-w-lg mx-auto text-center"> <a class="text-xs text-gray-500 hover:text-gray-600" href="{{ .LicensesURL }}">Open Source Licenses</a> </footer> <script>(function () { const advertiseExitNode = {{ .AdvertiseExitNode }}; const isUnraid = {{ .IsUnraid }}; const unraidCsrfToken = "{{ .UnraidToken }}"; let fetchingUrl = false; var data = { AdvertiseRoutes: "{{ .AdvertiseRoutes }}", AdvertiseExitNode: advertiseExitNode, Reauthenticate: false, ForceLogout: false }; function postData(e) { e.preventDefault(); if (fetchingUrl) { return; } fetchingUrl = true; const urlParams = new URLSearchParams(window.location.search); const token = urlParams.get("SynoToken"); const nextParams = new URLSearchParams({ up: true }); if (token) { nextParams.set("SynoToken", token) } const nextUrl = new URL(window.location); nextUrl.search = nextParams.toString() let body = JSON.stringify(data); let contentType = "application/json"; if (isUnraid) { const params = new URLSearchParams(); params.append("csrf_token", unraidCsrfToken); params.append("ts_data", JSON.stringify(data)); body = params.toString(); contentType = "application/x-www-form-urlencoded;charset=UTF-8"; } const url = nextUrl.toString(); fetch(url, { method: "POST", headers: { "Accept": "application/json", "Content-Type": contentType, }, body: body }).then(res => res.json()).then(res => { fetchingUrl = false; const err = res["error"]; if (err) { throw new Error(err); } const url = res["url"]; if (url) { if(isUnraid) { window.open(url, "_blank"); } else { document.location.href = url; } } else { location.reload(); } }).catch(err => { alert("Failed operation: " + err.message); }); } document.querySelectorAll(".js-loginButton").forEach(function (el){ el.addEventListener("click", function(e) { data.Reauthenticate = true; postData(e); }); }) document.querySelectorAll(".js-logoutButton").forEach(function(el) { el.addEventListener("click", function (e) { data.ForceLogout = true; postData(e); }); }) document.querySelectorAll(".js-advertiseExitNode").forEach(function (el) { el.addEventListener("click", function(e) { data.AdvertiseExitNode = !advertiseExitNode; postData(e); }); }) })();</script> </body> </html>