Add exit nodes to the panel
This commit is contained in:
@@ -52,6 +52,21 @@ function accountLabel(account) {
|
|||||||
return String(account.id || "Unknown 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) {
|
function parseStatus(raw) {
|
||||||
var text = String(raw || "").trim()
|
var text = String(raw || "").trim()
|
||||||
if (text === "") return { ok: true, unavailable: true, message: "Disconnected" }
|
if (text === "") return { ok: true, unavailable: true, message: "Disconnected" }
|
||||||
@@ -62,28 +77,24 @@ function parseStatus(raw) {
|
|||||||
var self = data.Self || {}
|
var self = data.Self || {}
|
||||||
var selfIps = filterIPv4(self.TailscaleIPs || data.TailscaleIPs || [])
|
var selfIps = filterIPv4(self.TailscaleIPs || data.TailscaleIPs || [])
|
||||||
var peers = []
|
var peers = []
|
||||||
|
var exitNodes = []
|
||||||
var rawPeers = data.Peer || {}
|
var rawPeers = data.Peer || {}
|
||||||
|
|
||||||
for (var id in rawPeers) {
|
for (var id in rawPeers) {
|
||||||
var peer = rawPeers[id] || {}
|
var peer = rawPeers[id] || {}
|
||||||
if (peer.Online !== true) continue
|
var normalized = peerFromStatus(id, peer)
|
||||||
peers.push({
|
if (normalized.Online) {
|
||||||
id: id,
|
peers.push(normalized)
|
||||||
HostName: displayHostName(peer.HostName, peer.DNSName),
|
if (normalized.ExitNodeOption) exitNodes.push(normalized)
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
peers.sort(function(a, b) {
|
peers.sort(function(a, b) {
|
||||||
return String(a.HostName).localeCompare(String(b.HostName))
|
return String(a.HostName).localeCompare(String(b.HostName))
|
||||||
})
|
})
|
||||||
|
exitNodes.sort(function(a, b) {
|
||||||
|
return String(a.HostName).localeCompare(String(b.HostName))
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -95,7 +106,8 @@ function parseStatus(raw) {
|
|||||||
selfName: displayHostName(self.HostName, self.DNSName),
|
selfName: displayHostName(self.HostName, self.DNSName),
|
||||||
selfDnsName: cleanDnsName(self.DNSName),
|
selfDnsName: cleanDnsName(self.DNSName),
|
||||||
selfIp: selfIps.length > 0 ? selfIps[0] : "",
|
selfIp: selfIps.length > 0 ? selfIps[0] : "",
|
||||||
peers: peers
|
peers: peers,
|
||||||
|
exitNodes: exitNodes
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { ok: false, unavailable: true, message: "Status error", error: "Failed to parse tailscale status" }
|
return { ok: false, unavailable: true, message: "Status error", error: "Failed to parse tailscale status" }
|
||||||
@@ -143,6 +155,7 @@ if (typeof module !== "undefined") {
|
|||||||
displayHostName: displayHostName,
|
displayHostName: displayHostName,
|
||||||
osIcon: osIcon,
|
osIcon: osIcon,
|
||||||
accountLabel: accountLabel,
|
accountLabel: accountLabel,
|
||||||
|
peerFromStatus: peerFromStatus,
|
||||||
parseStatus: parseStatus,
|
parseStatus: parseStatus,
|
||||||
parseAccounts: parseAccounts
|
parseAccounts: parseAccounts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,14 +39,7 @@ Panel {
|
|||||||
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
||||||
readonly property bool showConnections: tailscale.accounts.length > 1 || tailscale.accountsAccessDenied
|
readonly property bool showConnections: tailscale.accounts.length > 1 || tailscale.accountsAccessDenied
|
||||||
readonly property bool showPeers: tailscale.running && tailscale.peers.length > 0
|
readonly property bool showPeers: tailscale.running && tailscale.peers.length > 0
|
||||||
readonly property var exitNodes: {
|
readonly property var exitNodes: tailscale.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 bool showExitNodes: tailscale.running && exitNodes.length > 0
|
readonly property bool showExitNodes: tailscale.running && exitNodes.length > 0
|
||||||
readonly property color iconColor: tailscale.running ? foreground : dim
|
readonly property color iconColor: tailscale.running ? foreground : dim
|
||||||
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent"
|
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent"
|
||||||
@@ -877,7 +870,7 @@ Panel {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: exitNodeGlyph
|
id: exitNodeGlyph
|
||||||
text: tailscale.osIcon(peer ? peer.OS : "")
|
text: ""
|
||||||
color: exitNodeRow.activeExitNode || exitNodeRow.settingExitNode ? root.foreground : root.dim
|
color: exitNodeRow.activeExitNode || exitNodeRow.settingExitNode ? root.foreground : root.dim
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: Style.font.body
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ Item {
|
|||||||
property string selfIp: ""
|
property string selfIp: ""
|
||||||
property string authUrl: ""
|
property string authUrl: ""
|
||||||
property var peers: []
|
property var peers: []
|
||||||
|
property var exitNodes: []
|
||||||
property var accounts: []
|
property var accounts: []
|
||||||
property string selectedAccountId: ""
|
property string selectedAccountId: ""
|
||||||
property string selectedAccountLabel: ""
|
property string selectedAccountLabel: ""
|
||||||
@@ -158,6 +159,7 @@ Item {
|
|||||||
selfIp = ""
|
selfIp = ""
|
||||||
authUrl = ""
|
authUrl = ""
|
||||||
peers = []
|
peers = []
|
||||||
|
exitNodes = []
|
||||||
accounts = []
|
accounts = []
|
||||||
selectedAccountId = ""
|
selectedAccountId = ""
|
||||||
selectedAccountLabel = ""
|
selectedAccountLabel = ""
|
||||||
@@ -188,6 +190,7 @@ Item {
|
|||||||
selfDnsName = parsed.selfDnsName
|
selfDnsName = parsed.selfDnsName
|
||||||
selfIp = parsed.selfIp
|
selfIp = parsed.selfIp
|
||||||
peers = parsed.running ? parsed.peers : []
|
peers = parsed.running ? parsed.peers : []
|
||||||
|
exitNodes = parsed.running ? parsed.exitNodes : []
|
||||||
|
|
||||||
if (needsLogin) statusText = "Needs login"
|
if (needsLogin) statusText = "Needs login"
|
||||||
else if (running) {
|
else if (running) {
|
||||||
@@ -253,8 +256,9 @@ Item {
|
|||||||
|
|
||||||
function setExitNode(peer) {
|
function setExitNode(peer) {
|
||||||
if (!installed || !running || !peer || exitNodeProcess.running) return
|
if (!installed || !running || !peer || exitNodeProcess.running) return
|
||||||
var target = exitNodeTarget(peer)
|
var active = peer.ExitNode === true
|
||||||
if (target === "") return
|
var target = active ? "" : exitNodeTarget(peer)
|
||||||
|
if (!active && target === "") return
|
||||||
_exitNodeOutput = ""
|
_exitNodeOutput = ""
|
||||||
_exitNodeError = ""
|
_exitNodeError = ""
|
||||||
settingExitNodeId = String(peer.id || "")
|
settingExitNodeId = String(peer.id || "")
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ const status = tailscale.parseStatus(JSON.stringify({
|
|||||||
Online: false,
|
Online: false,
|
||||||
OS: 'linux'
|
OS: 'linux'
|
||||||
},
|
},
|
||||||
|
offlineExit: {
|
||||||
|
HostName: 'mbu-ser9',
|
||||||
|
DNSName: 'mbu-ser9.tail32f559.ts.net.',
|
||||||
|
TailscaleIPs: ['100.125.28.77', 'fd7a:115c:a1e0::1037:1c4d'],
|
||||||
|
Online: false,
|
||||||
|
OS: 'linux',
|
||||||
|
ExitNodeOption: true,
|
||||||
|
ExitNode: false
|
||||||
|
},
|
||||||
onlineA: {
|
onlineA: {
|
||||||
HostName: 'alpha',
|
HostName: 'alpha',
|
||||||
DNSName: 'alpha.tail32f559.ts.net.',
|
DNSName: 'alpha.tail32f559.ts.net.',
|
||||||
@@ -62,6 +71,7 @@ assertEqual(status.selfIp, '100.74.97.73', 'tailscale parses self IP')
|
|||||||
assertDeepEqual(status.peers.map(peer => peer.HostName), ['alpha', 'zed'], 'tailscale filters offline peers and sorts online peers')
|
assertDeepEqual(status.peers.map(peer => peer.HostName), ['alpha', 'zed'], 'tailscale filters offline peers and sorts online peers')
|
||||||
assertDeepEqual(status.peers[0].TailscaleIPv6, ['fd7a:115c:a1e0::1901:334b'], 'tailscale preserves peer IPv6 addresses for copy menu')
|
assertDeepEqual(status.peers[0].TailscaleIPv6, ['fd7a:115c:a1e0::1901:334b'], 'tailscale preserves peer IPv6 addresses for copy menu')
|
||||||
assert(status.peers[1].ExitNodeOption && status.peers[1].ExitNode, 'tailscale preserves exit node flags')
|
assert(status.peers[1].ExitNodeOption && status.peers[1].ExitNode, 'tailscale preserves exit node flags')
|
||||||
|
assertDeepEqual(status.exitNodes.map(peer => peer.HostName), ['zed'], 'tailscale lists only online exit nodes')
|
||||||
|
|
||||||
const stopped = tailscale.parseStatus(JSON.stringify({
|
const stopped = tailscale.parseStatus(JSON.stringify({
|
||||||
BackendState: 'Stopped',
|
BackendState: 'Stopped',
|
||||||
|
|||||||
Reference in New Issue
Block a user