feat: add peer list with click-to-copy (#6)
- Parse Peers from tailscale status --json into a model - Add ListView with hostname/IP rows below status row - Click hostname or IP to copy via wl-copy + toast confirmation - Increase popout height to 400 to accommodate peer list - Online peers shown in primary color, offline in surfaceVariantText
This commit is contained in:
parent
f980f4da21
commit
0d3f500a2c
2 changed files with 88 additions and 2 deletions
|
|
@ -73,3 +73,13 @@ If this project is used in a completely local-only or air-gapped context, refer
|
|||
- For self-hosted instances, include `--host` in commands unless the git remote already points at the correct instance.
|
||||
- `interrogate` and `to-prd` should read this file to know which CLI and host conventions to follow.
|
||||
- When in doubt about the current instance, check `git remote -v` first.
|
||||
|
||||
## Troubleshooting `fj` Failures
|
||||
|
||||
If a `fj` command fails on this or any self-hosted Forgejo instance, do not retry with different flags. Diagnose first:
|
||||
|
||||
1. `git remote -v` — note the URL form. URL-style remotes with explicit non-default ports (`ssh://git@host:NNNN/...`) cause `fj` to use that same port for HTTPS API calls, producing TLS errors. SCP-style remotes (`git@host:owner/repo.git`) avoid this; route non-default SSH ports via `~/.ssh/config`.
|
||||
2. `cat ~/.local/share/forgejo-cli/keys.json` — the host key string is what `fj` uses for both auth lookup and API URL. A `:NNNN` suffix here is the most common root cause.
|
||||
3. `fj whoami` (in-repo) and `fj -H https://<host> whoami` — comparing these tells you whether auth is stored under the bare host or under host-with-port.
|
||||
|
||||
`fj`'s syntax also has sharp edges worth noting: `fj issue comment <N> [BODY]` takes the body as a positional argument (not `--body`); there is no `fj issue list` — use `fj issue search "" -s open|closed|all`; and cross-repo issue references use `owner/repo#N`. When in doubt, run `<subcommand> --help` — `fj` is `clap`-based and its help output is reliable.
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ PluginComponent {
|
|||
property bool isConnected: false
|
||||
property string tailscaleIP: ""
|
||||
property string currentExitNode: ""
|
||||
property var peers: []
|
||||
|
||||
layerNamespacePlugin: "tailscalectl"
|
||||
popoutWidth: 360
|
||||
popoutHeight: 200
|
||||
popoutHeight: 400
|
||||
|
||||
Timer {
|
||||
interval: 5000
|
||||
|
|
@ -46,10 +47,19 @@ PluginComponent {
|
|||
root.isConnected = data.BackendState === "Running"
|
||||
root.tailscaleIP = (data.Self?.TailscaleIPs?.[0]) || ""
|
||||
root.currentExitNode = data.CurrentExitNode?.HostName || ""
|
||||
root.peers = Object.keys(data.Peers || {}).map(function(key) {
|
||||
var p = data.Peers[key]
|
||||
return {
|
||||
hostname: p.HostName || key,
|
||||
ip: (p.TailscaleIPs && p.TailscaleIPs.length) ? p.TailscaleIPs[0] : "",
|
||||
online: p.Online || false
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
root.isConnected = false
|
||||
root.tailscaleIP = ""
|
||||
root.currentExitNode = ""
|
||||
root.peers = []
|
||||
ToastService.showError("tailscalectl", "Failed to parse Tailscale status")
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +70,7 @@ PluginComponent {
|
|||
root.isConnected = false
|
||||
root.tailscaleIP = ""
|
||||
root.currentExitNode = ""
|
||||
root.peers = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +84,11 @@ PluginComponent {
|
|||
toggleProcess.running = true
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
Quickshell.execDetached(["sh", "-c", "echo -n '" + text + "' | wl-copy"])
|
||||
ToastService.showInfo("tailscalectl", "Copied: " + text)
|
||||
}
|
||||
|
||||
popoutContent: Component {
|
||||
PopoutComponent {
|
||||
headerText: "Tailscale"
|
||||
|
|
@ -80,8 +96,9 @@ PluginComponent {
|
|||
showCloseButton: true
|
||||
|
||||
Item {
|
||||
id: contentItem
|
||||
width: parent.width
|
||||
height: Theme.spacingM + statusRow.implicitHeight + Theme.spacingS + exitNodeText.implicitHeight + Theme.spacingM
|
||||
height: Theme.spacingM + statusRow.implicitHeight + Theme.spacingM + peerList.height + Theme.spacingM
|
||||
|
||||
Row {
|
||||
id: statusRow
|
||||
|
|
@ -128,6 +145,65 @@ PluginComponent {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: peerList
|
||||
y: Theme.spacingM + statusRow.implicitHeight + Theme.spacingM
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
height: Math.min(root.peers.length * (Theme.fontSizeSmall + Theme.spacingXS), 200)
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
model: root.peers
|
||||
interactive: true
|
||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||
|
||||
delegate: Item {
|
||||
width: peerList.width
|
||||
height: Theme.fontSizeSmall + Theme.spacingXS
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: peerHostnameText.implicitWidth
|
||||
height: peerHostnameText.implicitHeight
|
||||
onClicked: {
|
||||
root.copyToClipboard(modelData.hostname)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: peerHostnameText
|
||||
text: modelData.hostname
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: modelData.online ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: peerIpText.implicitWidth
|
||||
height: peerIpText.implicitHeight
|
||||
onClicked: {
|
||||
root.copyToClipboard(modelData.ip)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: peerIpText
|
||||
text: modelData.ip
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue