mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
icon and device type guessing from @slammingprogramming
This commit is contained in:
@@ -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.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user