feat(web): remove devices from the player UI (refs #451)

The merge of soundtouch-web into soundtouch-service was asymmetric:
manual device *adds* propagated to the player UI (HandleAddManualDevice
notifies, the hook re-seeds + broadcasts), but *removals* did not. The
datastore-removal handler never notified, and the web registry's sync
only ever added entries — its map was append-only, so a removed device
lingered in the player UI until restart.

This adds the missing removal path:

- DELETE /api/control/devices/{id} (HandleDeleteDevice). The registry is
  keyed by host/IP; the datastore by device ID (MAC), so the handler
  resolves one to the other via the connection's DeviceInfo, cascades to
  the datastore through a new RemoveDeviceHook (embedded build only),
  prunes the in-memory entry, and broadcasts the updated list.
- WebApp.RemoveDevice prunes the registry and stops the per-device
  goroutines (status poller + WebSocket reconnect loop) via a new
  done-channel + Close() on DeviceConnection — previously both ran for
  the life of the process.
- Server.RemoveDeviceByID extracts the cross-account lookup + remove from
  HandleRemoveDevice and now fires notifyDevicesChanged, so the admin
  Devices tab removal also propagates to the player UI.
- Player UI: a quiet per-card Remove control (visible on hover), a
  confirm dialog, optimistic prune, and a note that a still-online
  device may reappear after the next discovery scan (honest v1 — no
  ignore-list).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-06-07 15:08:26 +02:00
co-authored by Claude Opus 4.8
parent e82bb43988
commit b861c11d37
14 changed files with 393 additions and 30 deletions
+7
View File
@@ -1125,6 +1125,13 @@ func newEmbeddedWebApp(server *handlers.Server, serverURL string, ds *datastore.
// UI "discover" runs the service's sweep, not a second mDNS stack.
webApp.TriggerDiscovery = server.DiscoverDevices
// A removal from the player UI cascades to the datastore (the single
// source of truth), so the device does not reappear on the next re-sync.
webApp.RemoveDeviceHook = func(deviceID string) error {
_, err := server.RemoveDeviceByID(deviceID)
return err
}
// Keep the UI registry live as the service discovers or devices are added.
server.SetDevicesChangedHook(func() {
webApp.SeedExtraDevices()
+1
View File
@@ -4,6 +4,7 @@ DELETE /accounts/{account}/devices/{device} handlers.(
DELETE /accounts/{account}/group handlers.(*Server).HandleUnsupported-fm
DELETE /accounts/{account}/group/ handlers.(*Server).HandleUnsupported-fm
DELETE /accounts/{account}/group/{groupId} handlers.(*Server).HandleUnsupported-fm
DELETE /api/control/devices/{id}/ soundtouchweb.(*WebApp).HandleDeleteDevice-fm
DELETE /api/setup/devices/{deviceId} handlers.(*Server).HandleRemoveDevice-fm
DELETE /api/setup/dns-discoveries handlers.(*Server).HandleClearDNSDiscoveries-fm
DELETE /api/setup/interactions/sessions handlers.(*Server).HandleCleanupSessions-fm