Replace status-Addrs public IP with user-triggered curl probes (api.ipify.org, icanhazip.com). Shown as "tap to check" until fetched; cleared on disconnect or exit-node change. Direct argv, fallback chain, busy-mutex shared with other actions. v0.2.3 Written by AI agent working for @jtmorris. Model: Grok 4.5.
107 lines
3.9 KiB
Markdown
107 lines
3.9 KiB
Markdown
# Tailscale Widget Plugin for Dank Material Shell
|
||
|
||
A lightweight widget plugin that shows Tailscale connectivity status on the Dank Bar with quick controls for toggling connection, switching exit nodes, and copying peer addresses.
|
||
|
||

|
||
|
||
## Features
|
||
|
||
- **Status icon** in the bar — `vpn_key` when connected, `vpn_key_off` when disconnected
|
||
- **Right-click** to toggle Tailscale on/off
|
||
- **Left-click** to open a popout showing:
|
||
- Your current Tailscale IP (when connected)
|
||
- Public IP via **on-demand true egress check** (tap to run `curl` to ipify/icanhazip; not auto-fetched on status poll)
|
||
- Active exit node (with clear button)
|
||
- Peer list with hostnames and IPs (when connected)
|
||
- A clear "Not connected" empty state when disconnected (no stale peer list)
|
||
- **Click-to-copy** any hostname or IP to clipboard
|
||
- **Exit node selection** — click `↗` on any exit-node-capable peer to route through it
|
||
- **On-demand status** — polls Tailscale for ground truth on load, explicit actions, and post-mutation verification (defensive poll-act-poll for toggles; no always-on timer)
|
||
- **Single-flight actions** — concurrent status/toggle/exit/copy chains are rejected while an operation is in flight
|
||
- **Toast notifications** for all errors
|
||
|
||
## Requirements
|
||
|
||
- Dank Material Shell installed and running
|
||
- `tailscale` CLI available on `PATH`
|
||
- A clipboard tool (`dms` or `wl-copy`)
|
||
|
||
## Installation
|
||
|
||
1. Clone or copy this repository so that the `tailscalectl/` directory is available:
|
||
|
||
```
|
||
git clone <repo-url>
|
||
```
|
||
|
||
2. Place the `tailscalectl/` directory into your DMS plugins directory:
|
||
|
||
```
|
||
mv tailscalectl ~/.config/dankmaterialshell/plugins/
|
||
```
|
||
|
||
The directory must contain:
|
||
```
|
||
tailscalectl/
|
||
├── plugin.json
|
||
├── TailscaleWidget.qml
|
||
├── lib.js
|
||
└── i18n/
|
||
```
|
||
|
||
3. Reload the plugin:
|
||
|
||
```
|
||
dms ipc call plugins reload tailscalectl
|
||
```
|
||
|
||
4. The Tailscale icon should appear on your Dank Bar. If connected, it shows a filled key icon; if disconnected, an outlined key icon.
|
||
|
||
## Usage
|
||
|
||
| Action | How |
|
||
|---|---|
|
||
| Toggle connection | Right-click the bar icon |
|
||
| Open popout | Left-click the bar icon |
|
||
| Copy hostname/IP | Click the text in the peer list |
|
||
| Set exit node | Click `↗` next to an exit-node peer |
|
||
| Clear exit node | Click `×` next to "Exit node: ..." or click `↗` on current node |
|
||
|
||
## Plugin Manifest
|
||
|
||
```json
|
||
{
|
||
"id": "tailscalectl",
|
||
"name": "Tailscale",
|
||
"description": "Tailscale status and controls on the Dank Bar",
|
||
"author": "John Morris",
|
||
"icon": "vpn_key",
|
||
"type": "widget",
|
||
"capabilities": ["dankbar-widget"],
|
||
"component": "./TailscaleWidget.qml",
|
||
"permissions": ["process"],
|
||
"requires": ["tailscale"],
|
||
"version": "0.2.3"
|
||
}
|
||
```
|
||
|
||
## Implementation notes
|
||
|
||
- Uses `Proc` singleton (from `qs.Common`) for all external `tailscale` commands (one-shot stdout capture + auto cleanup).
|
||
- Fully I18n-ready via `I18n.tr(...)` (source keys in American English only today; see `tailscalectl/i18n/` for scaffolding).
|
||
- Follows current `dms-plugin-dev` + DMS 1.4 plugin best practices (capabilities, requires, no raw Process for one-shots, etc.).
|
||
- Toggle uses intentional defensive poll-act-poll; a failed status poll aborts the pending toggle (does not invent `up`/`down`).
|
||
- When `BackendState` is not `Running`, peer list / exit node / self IP are cleared so the UI never shows a stale connected-looking peer list.
|
||
- Public IP is a **lazy true-egress lookup**: tap "Public IP: tap to check" to probe via `curl` (`api.ipify.org`, then `icanhazip.com`). Cleared on disconnect or exit-node change. Not derived from `Self.Addrs`.
|
||
- Status row uses `RowLayout` with a real `Layout.fillWidth` spacer (not a no-op on plain `Row`).
|
||
- Peer `ListView` uses `Flickable.StopAtBounds` (no desktop rubber-band overshoot).
|
||
|
||
## Testing
|
||
|
||
```bash
|
||
node --test test/lib.test.js
|
||
```
|
||
|
||
## License
|
||
|
||
See [LICENSE](LICENSE).
|