station: Default device type to "unknown" and propagate DHCP hostname

GuessDeviceType silently returned "mobile" for any device whose hostname
or MAC OUI didn't match a known pattern, so the UI labelled every
unidentified device as a phone. Default to "unknown" instead and broaden
hostname matching (pixel/galaxy, thinkpad, imac/-pc, QEMU OUI).

The hostapd backend was also dropping DHCP hostnames on the floor: the
correlator only forwarded MAC->IP, and convertStation hard-coded the
hostname to "". Replace UpdateIPMapping with UpdateLeaseInfo that carries
both maps so hostnames flow through to ConnectedDevice.Name.

Frontend gains a "Sans nom" fallback when no hostname is available and
French labels for the device-type badge.
This commit is contained in:
nemunaire 2026-05-01 22:21:14 +08:00
commit a758c331c0
4 changed files with 68 additions and 35 deletions

View file

@ -27,17 +27,19 @@ type Backend struct {
stopCh chan struct{}
stopOnce sync.Once
// IP correlation - will be populated by periodic DHCP lease correlation
ipByMAC map[string]string // MAC -> IP mapping
// IP / hostname correlation - populated by periodic DHCP lease correlation
ipByMAC map[string]string // MAC -> IP mapping
hostnameByMAC map[string]string // MAC -> hostname mapping
}
// NewBackend creates a new hostapd backend
func NewBackend() *Backend {
return &Backend{
stations: make(map[string]*HostapdStation),
ipByMAC: make(map[string]string),
hostapdCLI: "hostapd_cli",
stopCh: make(chan struct{}),
stations: make(map[string]*HostapdStation),
ipByMAC: make(map[string]string),
hostnameByMAC: make(map[string]string),
hostapdCLI: "hostapd_cli",
stopCh: make(chan struct{}),
}
}
@ -298,12 +300,8 @@ func (b *Backend) parseAllStaOutput(output string) map[string]*HostapdStation {
// convertStation converts HostapdStation to backend.Station
func (b *Backend) convertStation(mac string, hs *HostapdStation) backend.Station {
// Get IP address if available from correlation
ip := b.ipByMAC[mac]
// Attempt hostname resolution if we have an IP
hostname := ""
// TODO: Could do reverse DNS lookup here if needed
hostname := b.hostnameByMAC[mac]
return backend.Station{
MAC: mac,
@ -317,27 +315,34 @@ func (b *Backend) convertStation(mac string, hs *HostapdStation) backend.Station
}
}
// UpdateIPMapping updates the MAC -> IP mapping from external source (e.g., DHCP)
// This should be called periodically to correlate hostapd stations with IP addresses
func (b *Backend) UpdateIPMapping(macToIP map[string]string) {
// UpdateLeaseInfo updates MAC -> IP and MAC -> hostname mappings from an
// external source (e.g., DHCP lease file). Called periodically by the
// correlator. An empty hostname leaves the existing one in place — DHCP
// leases sometimes drop the hostname between renewals.
func (b *Backend) UpdateLeaseInfo(macToIP, macToHostname map[string]string) {
b.mu.Lock()
defer b.mu.Unlock()
// Track which stations got IP updates
updated := make(map[string]bool)
for mac, ip := range macToIP {
if oldIP, exists := b.ipByMAC[mac]; exists && oldIP != ip {
// IP changed
updated[mac] = true
} else if !exists {
// New IP mapping
if oldIP, exists := b.ipByMAC[mac]; !exists || oldIP != ip {
updated[mac] = true
}
b.ipByMAC[mac] = ip
}
// Trigger update callbacks for stations that got new/changed IPs
for mac, hostname := range macToHostname {
if hostname == "" {
continue
}
if oldName, exists := b.hostnameByMAC[mac]; !exists || oldName != hostname {
updated[mac] = true
}
b.hostnameByMAC[mac] = hostname
}
// Trigger update callbacks for stations whose info changed
for mac := range updated {
if station, exists := b.stations[mac]; exists {
if cb := b.callbacks.OnStationUpdated; cb != nil {