When the configured Ethernet interface holds a DHCP-assigned IPv4 at
startup, the app now skips wifi.Initialize / StartEventMonitoring and
guards every wifi.* wrapper against a nil backend. This prevents D-Bus
calls to fi.w1.wpa_supplicant1 from re-activating the daemon via
dbus-activation, honoring the "do nothing" intent of the Ethernet path.
The probed state is exposed in SystemStatus and rendered in the header
as a third pill ("Ethernet · <IP>"); a new "disabled" connectionState
covers the WiFi pill in this mode.
Hostapd already parsed signal strength and rx/tx counters but the
station -> ConnectedDevice conversion threw them away. Add signalDbm,
rxBytes, txBytes and connectedAt to the OpenAPI schema and the
ConnectedDevice model, and centralise the conversion in
station.ToConnectedDevice so handlers, the periodic refresh and the
event callbacks all serialise the same shape.
Two follow-on bugs surfaced while wiring this up:
- The hostapd backend only stored station entries on first contact.
Subsequent polls were dropped, so signal and byte counters never
refreshed. Reconcile updates in checkStationChanges.
- ConnectedAt was reset to time.Now() on every conversion. Track
FirstSeen on HostapdStation when the station joins, and preserve
the timestamp across periodic refreshes in app.go so the UI's
"connected since" badge is stable.
Frontend gains a metrics row on each device card with signal bars,
total traffic and a live duration. Falls back gracefully when a
backend (DHCP, ARP) doesn't expose these fields.