icon and device type guessing from @slammingprogramming
Some checks are pending
Code checks / check-url-paths (push) Waiting to run
docker / docker_dev (push) Waiting to run
Deploy MkDocs / deploy (push) Waiting to run

This commit is contained in:
jokob-sk
2025-07-06 17:08:11 +10:00
parent ac90bb702e
commit 8949bcb567

View File

@@ -1,13 +1,14 @@
import sys
from typing import Optional, List, Tuple, Dict
import subprocess
import conf
import os
import re
# Register NetAlertX directories
INSTALL_PATH="/app"
sys.path.extend([f"{INSTALL_PATH}/server"])
import subprocess
import conf
import os
import re
from helper import timeNowTZ, get_setting, get_setting_value, list_to_where, check_IP_format, sanitize_SQL_input
from logger import mylog
from const import vendorsPath, vendorsPathNewest, sql_generateGuid
@@ -439,8 +440,6 @@ def update_devices_data_from_scan (db):
default_icon = get_setting_value('NEWDEV_devIcon')
if get_setting_value('NEWDEV_replace_preset_icon'):
query = f"""SELECT * FROM Devices
WHERE devIcon in ('', 'null', '{default_icon}')
@@ -647,118 +646,307 @@ def query_MAC_vendor (pMAC):
# Icons
#===============================================================================
#-------------------------------------------------------------------------------
# Base64 encoded HTML string for FontAwesome icons
icons = {
"globe": "PGkgY2xhc3M9ImZhcyBmYS1nbG9iZSI+PC9pPg==", # globe icon
"phone": "PGkgY2xhc3M9ImZhcyBmYS1tb2JpbGUtYWx0Ij48L2k+",
"laptop": "PGkgY2xhc3M9ImZhIGZhLWxhcHRvcCI+PC9pPg==",
"printer": "PGkgY2xhc3M9ImZhIGZhLXByaW50ZXIiPjwvaT4=",
"router": "PGkgY2xhc3M9ImZhcyBmYS1yYW5kb20iPjwvaT4=",
"tv": "PGkgY2xhc3M9ImZhIGZhLXR2Ij48L2k+",
"desktop": "PGkgY2xhc3M9ImZhIGZhLWRlc2t0b3AiPjwvaT4=",
"tablet": "PGkgY2xhc3M9ImZhIGZhLXRhYmxldCI+PC9pPg==",
"watch": "PGkgY2xhc3M9ImZhIGZhLXdhbmNoIj48L2k+",
"camera": "PGkgY2xhc3M9ImZhIGZhLWNhbWVyYSI+PC9pPg==",
"home": "PGkgY2xhc3M9ImZhIGZhLWhvbWUiPjwvaT4=",
"apple": "PGkgY2xhc3M9ImZhYiBmYS1hcHBsZSI+PC9pPg==",
"ethernet": "PGkgY2xhc3M9ImZhcyBmYS1ldGhlcm5ldCI+PC9pPg==",
"google": "PGkgY2xhc3M9ImZhYiBmYS1nb29nbGUiPjwvaT4=",
"raspberry": "PGkgY2xhc3M9ImZhYiBmYS1yYXNwYmVycnktcGkiPjwvaT4=",
"microchip": "PGkgY2xhc3M9ImZhcyBmYS1taWNyb2NoaXAiPjwvaT4="
# Base64 encoded HTML strings for FontAwesome icons, now with an extended icons dictionary for broader device coverage
ICONS = {
"globe": "PGkgY2xhc3M9ImZhcyBmYS1nbG9iZSI+PC9pPg==", # Internet or global network
"phone": "PGkgY2xhc3M9ImZhcyBmYS1tb2JpbGUtYWx0Ij48L2k+", # Smartphone
"laptop": "PGkgY2xhc3M9ImZhIGZhLWxhcHRvcCI+PC9pPg==", # Laptop
"printer": "PGkgY2xhc3M9ImZhIGZhLXByaW50ZXIiPjwvaT4=", # Printer
"router": "PGkgY2xhc3M9ImZhcyBmYS1yYW5kb20iPjwvaT4=", # Router or network switch
"tv": "PGkgY2xhc3M9ImZhIGZhLXR2Ij48L2k+", # Television
"desktop": "PGkgY2xhc3M9ImZhIGZhLWRlc2t0b3AiPjwvaT4=", # Desktop PC
"tablet": "PGkgY2xhc3M9ImZhIGZhLXRhYmxldCI+PC9pPg==", # Tablet
"watch": "PGkgY2xhc3M9ImZhcyBmYS1jbG9jayI+PC9pPg==", # Fallback to clock since smartwatch is nonfree in FontAwesome
"camera": "PGkgY2xhc3M9ImZhIGZhLWNhbWVyYSI+PC9pPg==", # Camera or webcam
"home": "PGkgY2xhc3M9ImZhIGZhLWhvbWUiPjwvaT4=", # Smart home device
"apple": "PGkgY2xhc3M9ImZhYiBmYS1hcHBsZSI+PC9pPg==", # Apple device
"ethernet": "PGkgY2xhc3M9ImZhcyBmYS1uZXR3b3JrLXdpcmVkIj48L2k+", # Free alternative for ethernet icon in FontAwesome
"google": "PGkgY2xhc3M9ImZhYiBmYS1nb29nbGUiPjwvaT4=", # Google device
"raspberry": "PGkgY2xhc3M9ImZhYiBmYS1yYXNwYmVycnktcGkiPjwvaT4=", # Raspberry Pi
"microchip": "PGkgY2xhc3M9ImZhcyBmYS1taWNyb2NoaXAiPjwvaT4=", # IoT or embedded device
"server": "PGkgY2xhc3M9ImZhcyBmYS1zZXJ2ZXIiPjwvaT4=", # Server
"gamepad": "PGkgY2xhc3M9ImZhcyBmYS1nYW1lcGFkIj48L2k+", # Gaming console
"lightbulb": "PGkgY2xhc3M9ImZhcyBmYS1saWdodGJ1bGIiPjwvaT4=", # Smart light
"speaker": "PGkgY2xhc3M9ImZhcyBmYS12b2x1bWUtdXAiPjwvaT4=", # Free speaker alt icon for smart speakers in FontAwesome
"lock": "PGkgY2xhc3M9ImZhcyBmYS1sb2NrIj48L2k+", # Security device
}
# Extended device types for comprehensive classification
DEVICE_TYPES = {
"Internet": "Internet Gateway",
"Phone": "Smartphone",
"Laptop": "Laptop",
"Printer": "Printer",
"Router": "Router",
"TV": "Television",
"Desktop": "Desktop PC",
"Tablet": "Tablet",
"Smartwatch": "Smartwatch",
"Camera": "Camera",
"SmartHome": "Smart Home Device",
"Server": "Server",
"GamingConsole": "Gaming Console",
"IoT": "IoT Device",
"NetworkSwitch": "Network Switch",
"AccessPoint": "Access Point",
"SmartLight": "Smart Light",
"SmartSpeaker": "Smart Speaker",
"SecurityDevice": "Security Device",
"Unknown": "Unknown Device",
}
#-------------------------------------------------------------------------------
# Guess device icon
def guess_icon(vendor, mac, ip, name, default):
mylog('debug', [f"[guess_icon] Guessing icon for (vendor|mac|ip|name): ('{vendor}'|'{mac}'|{ip}|{name})"])
result = default
mac = str(mac).upper() if mac else "00:00:00:00:00:00"
vendor = str(vendor).lower() if vendor else "unknown"
name = str(name).lower() if name else "(unknown)"
# Guess device attributes such as type of device and associated device icon
def guess_device_attributes(
vendor: Optional[str],
mac: Optional[str],
ip: Optional[str],
name: Optional[str],
default_icon: str,
default_type: str
) -> Tuple[str, str]:
"""
Guess the appropriate FontAwesome icon and device type based on device attributes.
# Guess icon based on vendor
if any(brand in vendor for brand in {"samsung", "motorola"}):
result = icons.get("phone")
elif "dell" in vendor:
result = icons.get("laptop")
elif "hp" in vendor:
result = icons.get("printer")
elif "cisco" in vendor:
result = icons.get("router")
elif "lg" in vendor:
result = icons.get("tv")
elif "raspberry" in vendor:
result = icons.get("raspberry")
elif "apple" in vendor:
result = icons.get("apple")
elif "google" in vendor:
result = icons.get("google")
elif "ubiquiti" in vendor:
result = icons.get("router")
elif any(brand in vendor for brand in {"espressif"}):
result = icons.get("microchip")
Args:
vendor: Device vendor name.
mac: Device MAC address.
ip: Device IP address.
name: Device name.
default_icon: Default icon to return if no match is found.
default_type: Default type to return if no match is found.
# Guess icon based on MAC address patterns
elif mac == "INTERNET":
result = icons.get("globe")
elif mac.startswith("00:1A:79"): # Apple
result = icons.get("apple")
elif mac.startswith("B0:BE:83"): # Apple
result = icons.get("apple")
elif mac.startswith("00:1B:63"): # Sony
result = icons.get("tablet")
elif mac.startswith("74:AC:B9"): # Unifi
result = icons.get("ethernet")
# Guess icon based on name
elif 'google' in name:
result = icons.get("google")
elif 'desktop' in name:
result = icons.get("desktop")
elif 'raspberry' in name:
result = icons.get("raspberry")
# Guess icon based on IP address ranges
elif ip.startswith("192.168.1."):
result = icons.get("laptop")
Returns:
Tuple[str, str]: A tuple containing the guessed icon (Base64-encoded HTML string)
and the guessed device type (string).
"""
mylog('debug', f"[guess_device_attributes] Guessing attributes for (vendor|mac|ip|name): ('{vendor}'|'{mac}'|'{ip}'|'{name}')")
# Normalize inputs
vendor = str(vendor).lower().strip() if vendor else "unknown"
mac = str(mac).upper().strip() if mac else "00:00:00:00:00:00"
ip = str(ip).strip() if ip else "169.254.0.0" # APIPA address for unknown IPs per RFC 3927
name = str(name).lower().strip() if name else "(unknown)"
return result
# --- Icon Guessing Logic ---
if mac == "INTERNET":
icon = ICONS.get("globe", default_icon)
else:
# Vendor-based icon guessing
icon_vendor_patterns = {
"apple": "apple",
"samsung|motorola|xiaomi|huawei": "phone",
"dell|lenovo|asus|acer": "laptop",
"hp|epson|canon|brother": "printer",
"cisco|ubiquiti|netgear|tp-link|d-link|mikrotik": "router",
"lg|samsung electronics|sony|vizio": "tv",
"raspberry pi": "raspberry",
"google": "google",
"espressif|particle": "microchip",
"intel|amd": "desktop",
"amazon": "speaker",
"philips hue|lifx": "lightbulb",
"aruba|meraki": "ethernet",
"qnap|synology": "server",
"nintendo|sony interactive|microsoft": "gamepad",
"ring|blink|arlo": "camera",
"nest": "home",
}
for pattern, icon_key in icon_vendor_patterns.items():
if re.search(pattern, vendor, re.IGNORECASE):
icon = ICONS.get(icon_key, default_icon)
break
else:
# MAC-based icon guessing
mac_clean = mac.replace(':', '').replace('-', '').upper()
icon_mac_patterns = {
"001A79|B0BE83|BC926B": "apple",
"001B63|BC4C4C": "tablet",
"74ACB9|002468": "ethernet",
"B827EB": "raspberry",
"001422|001874": "desktop",
"001CBF|002186": "server",
}
for pattern_str, icon_key in icon_mac_patterns.items():
patterns = [p.replace(':', '').replace('-', '').upper() for p in pattern_str.split('|')]
if any(mac_clean.startswith(p) for p in patterns):
icon = ICONS.get(icon_key, default_icon)
break
else:
# Name-based icon guessing
icon_name_patterns = {
"iphone|ipad|macbook|imac": "apple",
"pixel|galaxy|redmi": "phone",
"laptop|notebook": "laptop",
"printer|print": "printer",
"router|gateway|ap|access[ -]?point": "router",
"tv|television|smarttv": "tv",
"desktop|pc|computer": "desktop",
"tablet|pad": "tablet",
"watch|wear": "watch",
"camera|cam|webcam": "camera",
"echo|alexa|dot": "speaker",
"hue|lifx|bulb": "lightbulb",
"server|nas": "server",
"playstation|xbox|switch": "gamepad",
"raspberry|pi": "raspberry",
"google|chromecast|nest": "google",
"doorbell|lock|security": "lock",
}
for pattern, icon_key in icon_name_patterns.items():
if re.search(pattern, name, re.IGNORECASE):
icon = ICONS.get(icon_key, default_icon)
break
else:
# IP-based icon guessing
icon_ip_patterns = {
r"^192\.168\.[0-1]\.1$": "router",
r"^10\.0\.0\.1$": "router",
r"^192\.168\.[0-1]\.[2-9]$": "desktop",
r"^192\.168\.[0-1]\.1\d{2}$": "phone",
}
for pattern, icon_key in icon_ip_patterns.items():
if re.match(pattern, ip):
icon = ICONS.get(icon_key, default_icon)
break
else:
icon = default_icon
#-------------------------------------------------------------------------------
# Guess device type
def guess_type(vendor, mac, ip, name, default):
result = default
mac = str(mac).upper() if mac else "00:00:00:00:00:00"
vendor = str(vendor).lower() if vendor else "unknown"
name = str(name).lower() if name else "(unknown)"
# --- Type Guessing Logic ---
if mac == "INTERNET":
type_ = DEVICE_TYPES.get("Internet", default_type)
else:
# Vendor-based type guessing
type_vendor_patterns = {
"apple|samsung|motorola|xiaomi|huawei": "Phone",
"dell|lenovo|asus|acer|hp": "Laptop",
"epson|canon|brother": "Printer",
"cisco|ubiquiti|netgear|tp-link|d-link|mikrotik|aruba|meraki": "Router",
"lg|samsung electronics|sony|vizio": "TV",
"raspberry pi": "IoT",
"google|nest": "SmartHome",
"espressif|particle": "IoT",
"intel|amd": "Desktop",
"amazon": "SmartSpeaker",
"philips hue|lifx": "SmartLight",
"qnap|synology": "Server",
"nintendo|sony interactive|microsoft": "GamingConsole",
"ring|blink|arlo": "Camera",
}
for pattern, type_key in type_vendor_patterns.items():
if re.search(pattern, vendor, re.IGNORECASE):
type_ = DEVICE_TYPES.get(type_key, default_type)
break
else:
# MAC-based type guessing
mac_clean = mac.replace(':', '').replace('-', '').upper()
type_mac_patterns = {
"00:1A:79|B0:BE:83|BC:92:6B": "Phone",
"00:1B:63|BC:4C:4C": "Tablet",
"74:AC:B9|00:24:68": "AccessPoint",
"B8:27:EB": "IoT",
"00:14:22|00:18:74": "Desktop",
"00:1C:BF|00:21:86": "Server",
}
for pattern_str, type_key in type_mac_patterns.items():
patterns = [p.replace(':', '').replace('-', '').upper() for p in pattern_str.split('|')]
if any(mac_clean.startswith(p) for p in patterns):
type_ = DEVICE_TYPES.get(type_key, default_type)
break
else:
# Name-based type guessing
type_name_patterns = {
"iphone|ipad": "Phone",
"macbook|imac": "Laptop",
"pixel|galaxy|redmi": "Phone",
"laptop|notebook": "Laptop",
"printer|print": "Printer",
"router|gateway|ap|access[ -]?point": "Router",
"tv|television|smarttv": "TV",
"desktop|pc|computer": "Desktop",
"tablet|pad": "Tablet",
"watch|wear": "Smartwatch",
"camera|cam|webcam": "Camera",
"echo|alexa|dot": "SmartSpeaker",
"hue|lifx|bulb": "SmartLight",
"server|nas": "Server",
"playstation|xbox|switch": "GamingConsole",
"raspberry|pi": "IoT",
"google|chromecast|nest": "SmartHome",
"doorbell|lock|security": "SecurityDevice",
}
for pattern, type_key in type_name_patterns.items():
if re.search(pattern, name, re.IGNORECASE):
type_ = DEVICE_TYPES.get(type_key, default_type)
break
else:
# IP-based type guessing
type_ip_patterns = {
r"^192\.168\.[0-1]\.1$": "Router",
r"^10\.0\.0\.1$": "Router",
r"^192\.168\.[0-1]\.[2-9]$": "Desktop",
r"^192\.168\.[0-1]\.1\d{2}$": "Phone",
}
for pattern, type_key in type_ip_patterns.items():
if re.match(pattern, ip):
type_ = DEVICE_TYPES.get(type_key, default_type)
break
else:
type_ = default_type
# Guess icon based on vendor
if any(brand in vendor for brand in {"samsung", "motorola"}):
result = "Phone"
elif "cisco" in vendor:
result = "Router"
elif "lg" in vendor:
result = "TV"
elif "google" in vendor:
result = "Phone"
elif "ubiquiti" in vendor:
result = "Router"
return icon, type_
# Guess type based on MAC address patterns
elif mac == "INTERNET":
result = "Internet"
# Guess type based on name
elif 'google' in name:
result = "Phone"
# Deprecated functions with redirects (To be removed once all calls for these have been adjusted to use the updated function)
def guess_icon(
vendor: Optional[str],
mac: Optional[str],
ip: Optional[str],
name: Optional[str],
default: str
) -> str:
"""
[DEPRECATED] Guess the appropriate FontAwesome icon for a device based on its attributes.
Use guess_device_attributes instead.
Args:
vendor: Device vendor name.
mac: Device MAC address.
ip: Device IP address.
name: Device name.
default: Default icon to return if no match is found.
Returns:
str: Base64-encoded FontAwesome icon HTML string.
"""
# Guess type based on IP address ranges
elif ip == ("192.168.1.1"):
result = "Router"
icon, _ = guess_device_attributes(vendor, mac, ip, name, default, "unknown_type")
return icon
def guess_type(
vendor: Optional[str],
mac: Optional[str],
ip: Optional[str],
name: Optional[str],
default: str
) -> str:
"""
[DEPRECATED] Guess the device type based on its attributes.
Use guess_device_attributes instead.
Args:
vendor: Device vendor name.
mac: Device MAC address.
ip: Device IP address.
name: Device name.
default: Default type to return if no match is found.
Returns:
str: Device type from DEVICE_TYPES dictionary.
"""
return result
_, type_ = guess_device_attributes(vendor, mac, ip, name, "unknown_icon", default)
return type_
# Handler for when this is run as a program instead of called as a module.
if __name__ == "__main__":
mylog('error', "This module is not intended to be run directly.")