fix: remove binaryAvailable, fix statusCheck parser location, add timer guard

- Revert binaryAvailable property: reintroduced stale-state anti-pattern
  already rejected in #11. All error paths already handled by Process
  onExited handlers.
- Move statusCheck parsing from onStreamFinished to onExited (#38):
  parser now only runs after exit code validation, preventing stale
  data from being applied on failure.
- Add refreshTimer guard (#44): timer no longer spawns new statusCheck
  while one is already running.

Closes #38, #44. Reverts #52 partial fix.
This commit is contained in:
Vybe (Coding Agent) 2026-05-21 20:58:47 +00:00
parent fdad635b10
commit 30de75ff06

View file

@ -11,7 +11,6 @@ PluginComponent {
id: root id: root
property bool isConnected: false property bool isConnected: false
property bool binaryAvailable: true
property string tailscaleIP: "" property string tailscaleIP: ""
property string currentExitNode: "" property string currentExitNode: ""
property var peers: [] property var peers: []
@ -25,10 +24,15 @@ PluginComponent {
popoutHeight: 400 popoutHeight: 400
Timer { Timer {
id: refreshTimer
interval: 5000 interval: 5000
running: true running: true
repeat: true repeat: true
onTriggered: statusCheck.running = true onTriggered: {
if (!statusCheck.running) {
statusCheck.running = true
}
}
} }
Component.onCompleted: { Component.onCompleted: {
@ -91,46 +95,31 @@ PluginComponent {
command: ["tailscale", "status", "--json"] command: ["tailscale", "status", "--json"]
stdout: StdioCollector { stdout: StdioCollector {}
onStreamFinished: {
const state = TailscaleLib.parseStatusResult(this.text) onExited: (code, status) => {
if (code === 0) {
const state = TailscaleLib.parseStatusResult(statusCheck.stdout.text)
root.isConnected = state.isConnected root.isConnected = state.isConnected
root.tailscaleIP = state.tailscaleIP root.tailscaleIP = state.tailscaleIP
root.currentExitNode = state.currentExitNode root.currentExitNode = state.currentExitNode
root.peers = state.peers root.peers = state.peers
} } else {
}
onExited: (code, status) => {
if (code !== 0) {
root.isConnected = false root.isConnected = false
root.tailscaleIP = "" root.tailscaleIP = ""
root.currentExitNode = "" root.currentExitNode = ""
root.peers = [] root.peers = []
if (code === 127) { ToastService.showError("tailscalectl", TailscaleLib.errorMessage("status"))
root.binaryAvailable = false
ToastService.showError("tailscalectl", "Tailscale binary not found")
} else {
ToastService.showError("tailscalectl", TailscaleLib.errorMessage("status"))
}
} }
} }
} }
function toggleTailscale() { function toggleTailscale() {
if (!root.binaryAvailable) {
ToastService.showError("tailscalectl", "Tailscale not available")
return
}
toggleProcess.command = TailscaleLib.buildToggleCommand(root.isConnected) toggleProcess.command = TailscaleLib.buildToggleCommand(root.isConnected)
toggleProcess.running = true toggleProcess.running = true
} }
function setExitNode(hostname) { function setExitNode(hostname) {
if (!root.binaryAvailable) {
ToastService.showError("tailscalectl", "Tailscale not available")
return
}
exitNodeProcess.command = TailscaleLib.makeExitNodeCommand(hostname) exitNodeProcess.command = TailscaleLib.makeExitNodeCommand(hostname)
exitNodeProcess.running = true exitNodeProcess.running = true
} }
@ -167,14 +156,6 @@ PluginComponent {
width: parent.width width: parent.width
height: Theme.spacingM + statusRow.implicitHeight + Theme.spacingM + peerList.height + Theme.spacingM height: Theme.spacingM + statusRow.implicitHeight + Theme.spacingM + peerList.height + Theme.spacingM
StyledText {
visible: !root.binaryAvailable
text: "Tailscale not available"
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
Row { Row {
id: statusRow id: statusRow
y: Theme.spacingM y: Theme.spacingM