Add exit nodes to the panel

This commit is contained in:
David Heinemeier Hansson
2026-05-27 08:44:32 +02:00
parent 67dd7a2953
commit 8350a792ac
4 changed files with 45 additions and 25 deletions
+27 -14
View File
@@ -52,6 +52,21 @@ function accountLabel(account) {
return String(account.id || "Unknown account")
}
function peerFromStatus(id, peer) {
return {
id: id,
HostName: displayHostName(peer.HostName, peer.DNSName),
DNSName: cleanDnsName(peer.DNSName),
TailscaleIPs: filterIPv4(peer.TailscaleIPs || []),
TailscaleIPv6: filterIPv6(peer.TailscaleIPs || []),
Online: peer.Online === true,
OS: String(peer.OS || ""),
Tags: peer.Tags || [],
ExitNodeOption: peer.ExitNodeOption === true,
ExitNode: peer.ExitNode === true
}
}
function parseStatus(raw) {
var text = String(raw || "").trim()
if (text === "") return { ok: true, unavailable: true, message: "Disconnected" }
@@ -62,28 +77,24 @@ function parseStatus(raw) {
var self = data.Self || {}
var selfIps = filterIPv4(self.TailscaleIPs || data.TailscaleIPs || [])
var peers = []
var exitNodes = []
var rawPeers = data.Peer || {}
for (var id in rawPeers) {
var peer = rawPeers[id] || {}
if (peer.Online !== true) continue
peers.push({
id: id,
HostName: displayHostName(peer.HostName, peer.DNSName),
DNSName: cleanDnsName(peer.DNSName),
TailscaleIPs: filterIPv4(peer.TailscaleIPs || []),
TailscaleIPv6: filterIPv6(peer.TailscaleIPs || []),
Online: true,
OS: String(peer.OS || ""),
Tags: peer.Tags || [],
ExitNodeOption: peer.ExitNodeOption === true,
ExitNode: peer.ExitNode === true
})
var normalized = peerFromStatus(id, peer)
if (normalized.Online) {
peers.push(normalized)
if (normalized.ExitNodeOption) exitNodes.push(normalized)
}
}
peers.sort(function(a, b) {
return String(a.HostName).localeCompare(String(b.HostName))
})
exitNodes.sort(function(a, b) {
return String(a.HostName).localeCompare(String(b.HostName))
})
return {
ok: true,
@@ -95,7 +106,8 @@ function parseStatus(raw) {
selfName: displayHostName(self.HostName, self.DNSName),
selfDnsName: cleanDnsName(self.DNSName),
selfIp: selfIps.length > 0 ? selfIps[0] : "",
peers: peers
peers: peers,
exitNodes: exitNodes
}
} catch (e) {
return { ok: false, unavailable: true, message: "Status error", error: "Failed to parse tailscale status" }
@@ -143,6 +155,7 @@ if (typeof module !== "undefined") {
displayHostName: displayHostName,
osIcon: osIcon,
accountLabel: accountLabel,
peerFromStatus: peerFromStatus,
parseStatus: parseStatus,
parseAccounts: parseAccounts
}
+2 -9
View File
@@ -39,14 +39,7 @@ Panel {
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
readonly property bool showConnections: tailscale.accounts.length > 1 || tailscale.accountsAccessDenied
readonly property bool showPeers: tailscale.running && tailscale.peers.length > 0
readonly property var exitNodes: {
var nodes = []
for (var i = 0; i < tailscale.peers.length; i++) {
var peer = tailscale.peers[i]
if (peer && peer.ExitNodeOption === true) nodes.push(peer)
}
return nodes
}
readonly property var exitNodes: tailscale.exitNodes
readonly property bool showExitNodes: tailscale.running && exitNodes.length > 0
readonly property color iconColor: tailscale.running ? foreground : dim
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent"
@@ -877,7 +870,7 @@ Panel {
Text {
id: exitNodeGlyph
text: tailscale.osIcon(peer ? peer.OS : "")
text: "󱇢"
color: exitNodeRow.activeExitNode || exitNodeRow.settingExitNode ? root.foreground : root.dim
font.family: root.fontFamily
font.pixelSize: Style.font.body
+6 -2
View File
@@ -20,6 +20,7 @@ Item {
property string selfIp: ""
property string authUrl: ""
property var peers: []
property var exitNodes: []
property var accounts: []
property string selectedAccountId: ""
property string selectedAccountLabel: ""
@@ -158,6 +159,7 @@ Item {
selfIp = ""
authUrl = ""
peers = []
exitNodes = []
accounts = []
selectedAccountId = ""
selectedAccountLabel = ""
@@ -188,6 +190,7 @@ Item {
selfDnsName = parsed.selfDnsName
selfIp = parsed.selfIp
peers = parsed.running ? parsed.peers : []
exitNodes = parsed.running ? parsed.exitNodes : []
if (needsLogin) statusText = "Needs login"
else if (running) {
@@ -253,8 +256,9 @@ Item {
function setExitNode(peer) {
if (!installed || !running || !peer || exitNodeProcess.running) return
var target = exitNodeTarget(peer)
if (target === "") return
var active = peer.ExitNode === true
var target = active ? "" : exitNodeTarget(peer)
if (!active && target === "") return
_exitNodeOutput = ""
_exitNodeError = ""
settingExitNodeId = String(peer.id || "")