Skip to content

feat(tailscale): add Tailscale control center widget#1875

Open
Gdetrane wants to merge 1 commit intoAvengeMedia:masterfrom
Gdetrane:feat/tailscale-widget
Open

feat(tailscale): add Tailscale control center widget#1875
Gdetrane wants to merge 1 commit intoAvengeMedia:masterfrom
Gdetrane:feat/tailscale-widget

Conversation

@Gdetrane
Copy link
Contributor

@Gdetrane Gdetrane commented Mar 1, 2026

Summary

Native Tailscale integration as a builtin control center plugin — search, filter,
and copy IPs or FQDNs of any node in the tailnet without opening the web admin
panel or using the CLI.

Motivation

Tailscale is a rapidly growing mesh VPN with a vibrant community, now shipping by
default in distros like Bazzite and other Fedora variants. Existing desktop
integrations (e.g., GNOME extensions) are limited and unreliable. This widget
provides quick, native access to tailnet device information directly from the DMS
control center.

Changes

Go backend (core/internal/server/tailscale/)

  • Local API client — polls /var/run/tailscale/tailscaled.sock via HTTP over
    Unix socket, no CLI shelling or external dependencies
  • Status parsing — converts /localapi/v0/status into lean state structs with
    peer sorting (online first, alphabetical), user ID resolution, mobile hostname
    fixup (localhost to DNS-derived name), and relative time formatting
  • IPC methods — tailscale.subscribe (streaming state updates),
    tailscale.getStatus (one-shot), tailscale.refresh (manual poll)
  • Daemon lifecycle handling — eager init with 30s background retry when
    tailscaled is not running at startup; polls keep running through daemon
    stop/restart cycles for automatic recovery
  • 18 unit tests covering status parsing, peer sorting, manager lifecycle,
    handler routing, and edge cases

QML frontend

  • TailscaleService singleton with ref counting and subscription management
    following the CupsService pattern
  • TailscaleWidget PluginComponent with control center tile (device_hub icon,
    online peer count) and expandable detail view
  • Filter chips — My Online / Online / All with live device counts using
    DankFilterChips
  • Searchable device list — fuzzy match on hostname, DNS name, and IP
  • Copy-to-clipboard buttons for Tailscale IPs and DNS names
  • Self device highlighted with accent tint and This device badge
  • Expandable cards showing DNS name, tags, owner, relay/direct info
  • All strings use I18n.tr() with translator context per CONTRIBUTING.md

Integration

  • Widget registered in WidgetModel.qml with DragDropGrid and DetailHost wiring
  • DMSService.qml signal added for tailscale state updates
  • DetailHost.qml height set to 350px (matching Bluetooth/VPN)

Daemon lifecycle handling

  • Tailscale running at startup: widget shows immediately
  • Tailscale started after DMS: widget appears within 30s (retry loop)
  • Tailscale stopped while running: widget shows Disconnected within 30s
  • Tailscale restarted: widget recovers automatically within 30s
  • Tailscale not installed: widget shows Not available, no errors

Test plan

  • Unit tests pass (make test, pre-commit hooks pass)
  • Widget appears when Tailscale is running
  • Widget shows Not available when Tailscale is not installed
  • Filter chips filter correctly (My Online / Online / All)
  • Search filters by hostname, IP, and DNS name
  • Copy buttons copy correct values to clipboard
  • Self device shows with accent highlight and This device badge
  • Expanding a card reveals DNS name, tags, owner
  • Daemon stop: widget shows Disconnected
  • Daemon restart: widget recovers automatically
  • DMS started without Tailscale, Tailscale started later: widget appears
  • Mobile devices show proper names (not localhost)

Tested on:

  • Desktop (RTX 2070S), CachyOS (Arch), DMS (git) v1.4.0, niri
  • ThinkPad X390, CachyOS (Arch), DMS (git) v1.4.0, niri

Tailscale is a rapidly growing mesh VPN with a vibrant community,
now shipping by default in distros like Bazzite and other Fedora
variants. Existing desktop integrations (e.g., GNOME extensions)
are limited. This adds a native DMS widget for quickly searching,
filtering, and copying IPs or FQDNs of any node in the tailnet,
without opening the web admin panel or using the CLI.

Go backend:
- New tailscale service polling the local API socket at
  /var/run/tailscale/tailscaled.sock via HTTP over Unix socket
- Parses /localapi/v0/status into lean state structs with peer
  sorting, user resolution, and relative time formatting
- IPC methods: tailscale.subscribe (streaming), tailscale.getStatus,
  tailscale.refresh
- Eager initialization with background retry when tailscaled is not
  running at startup, graceful recovery on daemon restart
- 18 unit tests covering parsing, manager lifecycle, and handlers

QML frontend:
- TailscaleService singleton with ref counting and subscription
  management following CupsService pattern
- TailscaleWidget PluginComponent with control center tile and
  expandable detail view
- Filter chips (My Online / Online / All) with device counts
- Searchable device list with fuzzy match on hostname, DNS, and IP
- Copy-to-clipboard buttons for Tailscale IPs and DNS names
- Self device highlighted with accent tint and "This device" badge
- Expandable cards showing DNS name, tags, owner, relay info
- Mobile device hostname resolution (localhost -> DNS name)
- All strings use I18n.tr() with translator context

Handles all daemon lifecycle states:
- Tailscale running at startup: widget shows immediately
- Tailscale started after DMS: widget appears within 30s
- Tailscale stopped while running: widget shows "Disconnected"
- Tailscale restarted: widget recovers automatically within 30s

Manually tested on:
- Desktop (RTX 2070S), CachyOS (Arch), niri
- ThinkPad X390, CachyOS (Arch), niri
@Purian23
Copy link
Collaborator

Purian23 commented Mar 1, 2026

Hi there, thanks for the PR. I think this is better suited as an optional plug-in rather a native DMS feature as it's best fit for a select number of users. I will refer to @bbedward for a final accessment.

@Purian23 Purian23 requested a review from bbedward March 1, 2026 18:56
@Gdetrane
Copy link
Contributor Author

Gdetrane commented Mar 1, 2026

image

Let me know if you want to see more screenshots

@Gdetrane
Copy link
Contributor Author

Gdetrane commented Mar 1, 2026

@Purian23 Hi, thanks for jumping in, that's fair. I thought this could be a nice built-in feature, given Tailscale's growth and it being included in some distros, but I totally see your point.

I'm still new to this repo, but I imagine this would require keeping the core Go logic and moving the QML logic to a plugin package, right?

Any feedback is welcome

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants