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:
parent
0797f7dd50
commit
a758c331c0
4 changed files with 68 additions and 35 deletions
|
|
@ -401,13 +401,17 @@ function displayDevices(devices) {
|
||||||
const deviceCard = document.createElement('div');
|
const deviceCard = document.createElement('div');
|
||||||
deviceCard.className = 'device-card';
|
deviceCard.className = 'device-card';
|
||||||
|
|
||||||
|
const displayName = device.name && device.name.trim() !== ''
|
||||||
|
? device.name
|
||||||
|
: 'Sans nom';
|
||||||
|
|
||||||
deviceCard.innerHTML = `
|
deviceCard.innerHTML = `
|
||||||
${getDeviceIcon(device.type)}
|
${getDeviceIcon(device.type)}
|
||||||
<div class="device-name">${escapeHtml(device.name)}</div>
|
<div class="device-name">${escapeHtml(displayName)}</div>
|
||||||
<div class="device-type">${device.type}</div>
|
<div class="device-type">${escapeHtml(formatDeviceType(device.type))}</div>
|
||||||
<div class="device-info">
|
<div class="device-info">
|
||||||
<div>${device.ip}</div>
|
<div>${escapeHtml(device.ip || '—')}</div>
|
||||||
<div style="font-size: 0.75rem;">${device.mac}</div>
|
<div style="font-size: 0.75rem;">${escapeHtml(device.mac)}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -415,6 +419,17 @@ function displayDevices(devices) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDeviceType(type) {
|
||||||
|
const labels = {
|
||||||
|
mobile: 'Mobile',
|
||||||
|
tablet: 'Tablette',
|
||||||
|
laptop: 'Portable',
|
||||||
|
desktop: 'Ordinateur',
|
||||||
|
unknown: 'Inconnu'
|
||||||
|
};
|
||||||
|
return labels[type] || labels.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
function addLogEntry(log) {
|
function addLogEntry(log) {
|
||||||
const logContainer = document.getElementById('logContainer');
|
const logContainer = document.getElementById('logContainer');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,19 +87,26 @@ type BackendConfig struct {
|
||||||
HostapdInterface string // Hostapd interface name for DBus
|
HostapdInterface string // Hostapd interface name for DBus
|
||||||
}
|
}
|
||||||
|
|
||||||
// GuessDeviceType attempts to guess device type from hostname and MAC address
|
// GuessDeviceType attempts to guess device type from hostname and MAC address.
|
||||||
|
// Returns "unknown" when no signal is strong enough — the frontend renders that
|
||||||
|
// as a neutral icon rather than mislabeling unknown devices as "mobile".
|
||||||
func GuessDeviceType(hostname, mac string) string {
|
func GuessDeviceType(hostname, mac string) string {
|
||||||
hostname = strings.ToLower(hostname)
|
hostname = strings.ToLower(hostname)
|
||||||
|
|
||||||
if strings.Contains(hostname, "iphone") || strings.Contains(hostname, "android") {
|
if strings.Contains(hostname, "iphone") || strings.Contains(hostname, "android") ||
|
||||||
|
strings.Contains(hostname, "pixel") || strings.Contains(hostname, "galaxy") {
|
||||||
return "mobile"
|
return "mobile"
|
||||||
} else if strings.Contains(hostname, "ipad") || strings.Contains(hostname, "tablet") {
|
} else if strings.Contains(hostname, "ipad") || strings.Contains(hostname, "tablet") {
|
||||||
return "tablet"
|
return "tablet"
|
||||||
} else if strings.Contains(hostname, "macbook") || strings.Contains(hostname, "laptop") {
|
} else if strings.Contains(hostname, "macbook") || strings.Contains(hostname, "laptop") ||
|
||||||
|
strings.Contains(hostname, "thinkpad") {
|
||||||
return "laptop"
|
return "laptop"
|
||||||
|
} else if strings.Contains(hostname, "imac") || strings.Contains(hostname, "desktop") ||
|
||||||
|
strings.Contains(hostname, "-pc") {
|
||||||
|
return "desktop"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guess by MAC prefix (OUI)
|
// Guess by MAC prefix (OUI) — VM hypervisor MACs are almost always laptops
|
||||||
if len(mac) >= 8 {
|
if len(mac) >= 8 {
|
||||||
macPrefix := strings.ToUpper(mac[:8])
|
macPrefix := strings.ToUpper(mac[:8])
|
||||||
switch macPrefix {
|
switch macPrefix {
|
||||||
|
|
@ -107,8 +114,8 @@ func GuessDeviceType(hostname, mac string) string {
|
||||||
return "laptop"
|
return "laptop"
|
||||||
case "08:00:27": // VirtualBox
|
case "08:00:27": // VirtualBox
|
||||||
return "laptop"
|
return "laptop"
|
||||||
default:
|
case "52:54:00": // QEMU/KVM
|
||||||
return "mobile"
|
return "laptop"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,19 @@ type Backend struct {
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
stopOnce sync.Once
|
stopOnce sync.Once
|
||||||
|
|
||||||
// IP correlation - will be populated by periodic DHCP lease correlation
|
// IP / hostname correlation - populated by periodic DHCP lease correlation
|
||||||
ipByMAC map[string]string // MAC -> IP mapping
|
ipByMAC map[string]string // MAC -> IP mapping
|
||||||
|
hostnameByMAC map[string]string // MAC -> hostname mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBackend creates a new hostapd backend
|
// NewBackend creates a new hostapd backend
|
||||||
func NewBackend() *Backend {
|
func NewBackend() *Backend {
|
||||||
return &Backend{
|
return &Backend{
|
||||||
stations: make(map[string]*HostapdStation),
|
stations: make(map[string]*HostapdStation),
|
||||||
ipByMAC: make(map[string]string),
|
ipByMAC: make(map[string]string),
|
||||||
hostapdCLI: "hostapd_cli",
|
hostnameByMAC: make(map[string]string),
|
||||||
stopCh: make(chan struct{}),
|
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
|
// convertStation converts HostapdStation to backend.Station
|
||||||
func (b *Backend) convertStation(mac string, hs *HostapdStation) backend.Station {
|
func (b *Backend) convertStation(mac string, hs *HostapdStation) backend.Station {
|
||||||
// Get IP address if available from correlation
|
|
||||||
ip := b.ipByMAC[mac]
|
ip := b.ipByMAC[mac]
|
||||||
|
hostname := b.hostnameByMAC[mac]
|
||||||
// Attempt hostname resolution if we have an IP
|
|
||||||
hostname := ""
|
|
||||||
// TODO: Could do reverse DNS lookup here if needed
|
|
||||||
|
|
||||||
return backend.Station{
|
return backend.Station{
|
||||||
MAC: mac,
|
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)
|
// UpdateLeaseInfo updates MAC -> IP and MAC -> hostname mappings from an
|
||||||
// This should be called periodically to correlate hostapd stations with IP addresses
|
// external source (e.g., DHCP lease file). Called periodically by the
|
||||||
func (b *Backend) UpdateIPMapping(macToIP map[string]string) {
|
// 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()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
// Track which stations got IP updates
|
|
||||||
updated := make(map[string]bool)
|
updated := make(map[string]bool)
|
||||||
|
|
||||||
for mac, ip := range macToIP {
|
for mac, ip := range macToIP {
|
||||||
if oldIP, exists := b.ipByMAC[mac]; exists && oldIP != ip {
|
if oldIP, exists := b.ipByMAC[mac]; !exists || oldIP != ip {
|
||||||
// IP changed
|
|
||||||
updated[mac] = true
|
|
||||||
} else if !exists {
|
|
||||||
// New IP mapping
|
|
||||||
updated[mac] = true
|
updated[mac] = true
|
||||||
}
|
}
|
||||||
b.ipByMAC[mac] = ip
|
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 {
|
for mac := range updated {
|
||||||
if station, exists := b.stations[mac]; exists {
|
if station, exists := b.stations[mac]; exists {
|
||||||
if cb := b.callbacks.OnStationUpdated; cb != nil {
|
if cb := b.callbacks.OnStationUpdated; cb != nil {
|
||||||
|
|
|
||||||
|
|
@ -81,14 +81,20 @@ func (dc *DHCPCorrelator) correlate() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build MAC -> IP mapping
|
// Build MAC -> IP and MAC -> hostname mappings. Hostnames live in the
|
||||||
|
// dhcpd "client-hostname" field and let us label devices instead of
|
||||||
|
// falling back to bare MAC addresses.
|
||||||
macToIP := make(map[string]string)
|
macToIP := make(map[string]string)
|
||||||
|
macToHostname := make(map[string]string)
|
||||||
for _, lease := range leases {
|
for _, lease := range leases {
|
||||||
macToIP[lease.MAC] = lease.IP
|
mac := strings.ToLower(lease.MAC)
|
||||||
|
macToIP[mac] = lease.IP
|
||||||
|
if lease.Hostname != "" {
|
||||||
|
macToHostname[mac] = lease.Hostname
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update backend with IP mappings
|
dc.backend.UpdateLeaseInfo(macToIP, macToHostname)
|
||||||
dc.backend.UpdateIPMapping(macToIP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseDHCPLeases reads and parses DHCP lease file
|
// parseDHCPLeases reads and parses DHCP lease file
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue