Improve Tailscale panel controls

This commit is contained in:
David Heinemeier Hansson
2026-06-06 15:48:56 +02:00
parent 1409f85745
commit d7d08d1b17
2 changed files with 38 additions and 24 deletions
+34 -20
View File
@@ -238,7 +238,7 @@ Panel {
if (focusSection === "header") { if (focusSection === "header") {
tailscale.toggleTailscale() tailscale.toggleTailscale()
} else if (focusSection === "auth") { } else if (focusSection === "auth") {
tailscale.authorizeProfileSwitching() tailscale.authorizeTailscaleOperator()
} else if (focusSection === "accounts") { } else if (focusSection === "accounts") {
var account = selectedAccount() var account = selectedAccount()
if (account) tailscale.switchAccount(account.id) if (account) tailscale.switchAccount(account.id)
@@ -748,7 +748,7 @@ Panel {
cursorShape: tailscale.busy ? Qt.ArrowCursor : Qt.PointingHandCursor cursorShape: tailscale.busy ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !tailscale.busy enabled: !tailscale.busy
onEntered: root.setAuthCursor() onEntered: root.setAuthCursor()
onClicked: tailscale.authorizeProfileSwitching() onClicked: tailscale.authorizeTailscaleOperator()
} }
RowLayout { RowLayout {
@@ -774,7 +774,7 @@ Panel {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: "Authorize switching" text: "Authorize Tailscale operator"
color: root.foreground color: root.foreground
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.body font.pixelSize: Style.font.body
@@ -783,7 +783,7 @@ Panel {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: "Allow this user to see and switch Tailscale connections" text: "Allow this user to operate this Tailscale profile"
color: root.dim color: root.dim
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.caption font.pixelSize: Style.font.caption
@@ -791,14 +791,6 @@ Panel {
} }
} }
PanelActionButton {
iconText: "󰄬"
foreground: root.foreground
fontFamily: root.fontFamily
enabled: !tailscale.busy
Layout.alignment: Qt.AlignVCenter
onClicked: tailscale.authorizeProfileSwitching()
}
} }
} }
@@ -994,14 +986,7 @@ Panel {
modal: false modal: false
focus: true focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpenedChanged: { function handleKey(event) {
root.copyMenuOpen = opened
if (opened) {
peerRow.clampCopyIndex()
forceActiveFocus()
}
}
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
close() close()
event.accepted = true event.accepted = true
@@ -1022,6 +1007,15 @@ Panel {
event.accepted = true event.accepted = true
} }
} }
onOpenedChanged: {
root.copyMenuOpen = opened
if (opened) {
peerRow.clampCopyIndex()
Qt.callLater(function() { copyPopupContent.forceActiveFocus() })
} else if (root.opened) {
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
}
}
background: BorderSurface { background: BorderSurface {
color: Color.background color: Color.background
borderSpec: Border.flat(root.dim, 1) borderSpec: Border.flat(root.dim, 1)
@@ -1029,7 +1023,11 @@ Panel {
} }
contentItem: Column { contentItem: Column {
id: copyPopupContent
width: parent.width width: parent.width
focus: true
Keys.priority: Keys.BeforeItem
Keys.onPressed: function(event) { copyPopup.handleKey(event) }
Repeater { Repeater {
model: peerRow.copyOptions model: peerRow.copyOptions
@@ -1102,6 +1100,7 @@ Panel {
readonly property bool activeExitNode: peer && peer.ExitNode === true readonly property bool activeExitNode: peer && peer.ExitNode === true
readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "") readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "")
readonly property string peerName: peer ? String(peer.DisplayName || peer.HostName || "Unknown") : "Unknown" readonly property string peerName: peer ? String(peer.DisplayName || peer.HostName || "Unknown") : "Unknown"
readonly property string actionTooltip: addMullvad ? "" : (activeExitNode ? "Disconnect" : "Connect")
hasCursor: root.cursorActive && root.focusSection === "exitNodes" && root.exitNodeIndex === rowIndex hasCursor: root.cursorActive && root.focusSection === "exitNodes" && root.exitNodeIndex === rowIndex
current: activeExitNode || settingExitNode || (addMullvad && root.mullvadPickerOpen) current: activeExitNode || settingExitNode || (addMullvad && root.mullvadPickerOpen)
@@ -1154,12 +1153,19 @@ Panel {
} }
MouseArea { MouseArea {
id: exitNodeMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onEntered: root.setExitNodeCursor(exitNodeRow.rowIndex) onEntered: root.setExitNodeCursor(exitNodeRow.rowIndex)
onClicked: root.chooseExitNode(exitNodeRow.peer) onClicked: root.chooseExitNode(exitNodeRow.peer)
} }
PanelToolTip {
visible: exitNodeRow.actionTooltip !== "" && exitNodeMouse.containsMouse
text: exitNodeRow.actionTooltip
fontFamily: root.fontFamily
}
} }
component MullvadRegionRow: CursorSurface { component MullvadRegionRow: CursorSurface {
@@ -1172,6 +1178,7 @@ Panel {
readonly property bool activeExitNode: peer && peer.ExitNode === true readonly property bool activeExitNode: peer && peer.ExitNode === true
readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "") readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "")
readonly property bool selectedRegion: root.mullvadPickerOpen && root.mullvadRegionIndex === rowIndex readonly property bool selectedRegion: root.mullvadPickerOpen && root.mullvadRegionIndex === rowIndex
readonly property string actionTooltip: activeExitNode ? "Disconnect" : "Connect"
foreground: root.foreground foreground: root.foreground
fill: root.hoverFill fill: root.hoverFill
@@ -1226,11 +1233,18 @@ Panel {
} }
MouseArea { MouseArea {
id: regionMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onEntered: root.mullvadRegionIndex = regionRow.rowIndex onEntered: root.mullvadRegionIndex = regionRow.rowIndex
onClicked: root.chooseExitNode(regionRow.peer) onClicked: root.chooseExitNode(regionRow.peer)
} }
PanelToolTip {
visible: regionMouse.containsMouse
text: regionRow.actionTooltip
fontFamily: root.fontFamily
}
} }
} }
+4 -4
View File
@@ -291,11 +291,11 @@ Item {
exitNodeProcess.running = true exitNodeProcess.running = true
} }
function authorizeProfileSwitching() { function authorizeTailscaleOperator() {
if (!installed || operatorProcess.running || userName === "") return if (!installed || operatorProcess.running || userName === "") return
_operatorOutput = "" _operatorOutput = ""
_operatorError = "" _operatorError = ""
actionStatus = "Authorizing Tailscale profiles…" actionStatus = "Authorizing Tailscale operator..."
operatorProcess.command = ["pkexec", "tailscale", "set", "--operator=" + userName] operatorProcess.command = ["pkexec", "tailscale", "set", "--operator=" + userName]
operatorProcess.running = true operatorProcess.running = true
} }
@@ -414,7 +414,7 @@ Item {
root.parseAccounts("") root.parseAccounts("")
if (/profiles access denied/i.test(stderr) || /profiles access denied/i.test(stdout)) { if (/profiles access denied/i.test(stderr) || /profiles access denied/i.test(stdout)) {
root.accountsAccessDenied = true root.accountsAccessDenied = true
root.lastError = "Authorize Tailscale profiles to show connections" root.lastError = "Authorize Tailscale operator to show connections"
} else { } else {
root.lastError = elideStatus(stderr || stdout || "Could not list Tailscale connections") root.lastError = elideStatus(stderr || stdout || "Could not list Tailscale connections")
} }
@@ -534,7 +534,7 @@ Item {
} else { } else {
root.accountsAccessDenied = false root.accountsAccessDenied = false
root.lastError = "" root.lastError = ""
root.actionStatus = "Tailscale profiles authorized" root.actionStatus = "Tailscale operator authorized"
actionStatusTimer.restart() actionStatusTimer.restart()
root._lastAccountsRefreshMs = 0 root._lastAccountsRefreshMs = 0
} }