mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
320 lines
13 KiB
Python
Executable File
320 lines
13 KiB
Python
Executable File
import sys
|
|
import re
|
|
from typing import Optional, List, Tuple, Dict
|
|
|
|
# Register NetAlertX directories
|
|
INSTALL_PATH = "/app"
|
|
sys.path.extend([f"{INSTALL_PATH}/server"])
|
|
|
|
import conf
|
|
from const import *
|
|
from logger import mylog
|
|
from helper import timeNowTZ, get_setting_value
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# 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 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.
|
|
|
|
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.
|
|
|
|
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)"
|
|
|
|
# --- 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
|
|
|
|
# --- 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
|
|
|
|
return icon, type_
|
|
|
|
# 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.
|
|
"""
|
|
|
|
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.
|
|
"""
|
|
|
|
_, 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.")
|
|
|