1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-18 09:05:24 -04:00

feat(tailscale): add connect/disconnect, exit-node and LAN-access controls (#2644)

* feat(tailscale): add connect/disconnect/exit-node/LAN-access backend

The Tailscale backend previously exposed only read-only status
(tailscale.getStatus, tailscale.refresh). This adds write actions through the
existing tailscale.com/client/local integration:

- tailscale.connect / tailscale.disconnect (EditPrefs WantRunning)
- tailscale.setExitNode (EditPrefs ExitNodeID; empty id clears it and any
  legacy ExitNodeIP, mirroring `tailscale set --exit-node`)
- tailscale.setAllowLanAccess (EditPrefs ExitNodeAllowLANAccess)

The manager's client interface gains GetPrefs/EditPrefs; fetchState merges
ExitNodeAllowLANAccess from prefs, and Peer exposes ExitNodeOption so the UI
can list exit-node-capable peers.

* feat(tailscale): expose the new actions in TailscaleService

Adds connectTailscale/disconnectTailscale, setExitNode/clearExitNode and
setAllowLanAccess wrappers, plus derived exitNodeOptions/currentExitNode and the
exitNodeAllowLanAccess state. Write-action errors surface via ToastService.

* feat(tailscale): add connection, exit-node and LAN-access controls to the widget

The control-center widget toggle was a no-op. It now connects/disconnects, and
the detail panel gains a connection status row with a connect/disconnect button,
an exit-node picker and a LAN-access toggle.
This commit is contained in:
Rocho
2026-06-16 15:08:22 +02:00
committed by GitHub
parent 2fd9de5062
commit 988b54515e
8 changed files with 499 additions and 39 deletions
@@ -25,7 +25,14 @@ PluginComponent {
}
ccWidgetIsActive: TailscaleService.connected
onCcWidgetToggled: {}
onCcWidgetToggled: {
if (!TailscaleService.available)
return;
if (TailscaleService.connected)
TailscaleService.disconnectTailscale(null);
else
TailscaleService.connectTailscale(null);
}
ccDetailContent: Component {
Rectangle {
@@ -88,6 +95,122 @@ PluginComponent {
width: parent.width
spacing: Theme.spacingS
// Connection status + connect/disconnect. Always shown
// (when available) so the connection can be toggled from
// the detail, including while disconnected.
RowLayout {
width: parent.width
spacing: Theme.spacingS
Column {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 1
StyledText {
text: TailscaleService.connected ? I18n.tr("Connected", "Tailscale connection status: connected") : I18n.tr("Disconnected", "Tailscale connection status: disconnected")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
visible: TailscaleService.connected && TailscaleService.tailnetName.length > 0
text: TailscaleService.tailnetName
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
elide: Text.ElideRight
}
}
Rectangle {
id: connButton
Layout.alignment: Qt.AlignVCenter
height: 28
radius: 14
width: connButtonRow.implicitWidth + Theme.spacingM * 2
readonly property bool isConnected: TailscaleService.connected
color: isConnected ? (connButtonArea.containsMouse ? Theme.errorHover : Theme.surfaceLight) : (connButtonArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight)
Row {
id: connButtonRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: connButton.isConnected ? "link_off" : "link"
size: Theme.fontSizeSmall
color: connButton.isConnected ? Theme.surfaceText : Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: connButton.isConnected ? I18n.tr("Disconnect", "Tailscale disconnect button") : I18n.tr("Connect", "Tailscale connect button")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: connButton.isConnected ? Theme.surfaceText : Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: connButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (TailscaleService.connected)
TailscaleService.disconnectTailscale(null);
else
TailscaleService.connectTailscale(null);
}
}
}
}
// Connection controls: exit node picker + LAN access.
// Only meaningful while the backend is connected.
Column {
id: controlsColumn
width: parent.width
spacing: Theme.spacingS
visible: TailscaleService.connected
readonly property string noneLabel: I18n.tr("None", "Tailscale exit node: none selected")
DankDropdown {
width: parent.width
text: I18n.tr("Exit node", "Tailscale exit node selector label")
currentValue: TailscaleService.currentExitNode ? TailscaleService.currentExitNode.hostname : controlsColumn.noneLabel
options: {
const opts = [controlsColumn.noneLabel];
for (const p of TailscaleService.exitNodeOptions)
opts.push(p.hostname);
return opts;
}
onValueChanged: value => {
if (value === controlsColumn.noneLabel) {
TailscaleService.clearExitNode(null);
return;
}
const peer = TailscaleService.exitNodeOptions.find(p => p.hostname === value);
if (peer)
TailscaleService.setExitNode(peer.id, null);
}
}
DankToggle {
width: parent.width
text: I18n.tr("Allow LAN access", "Tailscale allow LAN access toggle")
description: I18n.tr("Reach local network devices while using an exit node", "Tailscale allow LAN access description")
visible: TailscaleService.currentExitNode !== null
checked: TailscaleService.exitNodeAllowLanAccess
onToggled: value => TailscaleService.setAllowLanAccess(value, null)
}
}
// Search bar + refresh button
RowLayout {
width: parent.width