Agregado del tiempo de cada ping a la lista

This commit is contained in:
Miguel 2025-04-04 22:58:28 +02:00
parent ee37a10a97
commit 8f7bd969de
1 changed files with 211 additions and 80 deletions

View File

@ -173,6 +173,9 @@ class IPChangerApp:
self.continuous_ping = tk.BooleanVar(value=False) self.continuous_ping = tk.BooleanVar(value=False)
self.ping_running = False self.ping_running = False
self.ping_stop_event = threading.Event() self.ping_stop_event = threading.Event()
self.last_ping_time = tk.StringVar(
value="N/A"
) # Add variable for last ping time
# Variables for IP scanning # Variables for IP scanning
self.scan_results = [] self.scan_results = []
@ -263,7 +266,32 @@ class IPChangerApp:
self.log_frame = ttk.LabelFrame(self.master, text="Operation Log", padding="5") self.log_frame = ttk.LabelFrame(self.master, text="Operation Log", padding="5")
self.log_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=(0, 10)) self.log_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=(0, 10))
self.log_text = tk.Text(self.log_frame, wrap="none", height=10) # Use a better font and enable tag configuration
log_font = (
"Consolas",
10,
) # Use a monospaced font like Consolas or Courier New
self.log_text = tk.Text(self.log_frame, wrap="none", height=10, font=log_font)
# Create tags for different message types with specific formatting
self.log_text.tag_configure("INFO", foreground="blue")
self.log_text.tag_configure(
"ERROR", foreground="red", font=(log_font[0], log_font[1], "bold")
)
self.log_text.tag_configure(
"SUCCESS", foreground="green", font=(log_font[0], log_font[1], "bold")
)
self.log_text.tag_configure("WARN", foreground="orange")
self.log_text.tag_configure("PING", foreground="purple")
self.log_text.tag_configure(
"DISCOVERY", foreground="navy", font=(log_font[0], log_font[1], "bold")
)
self.log_text.tag_configure("HOSTNAME", foreground="teal")
self.log_text.tag_configure("MAC", foreground="maroon")
self.log_text.tag_configure("COMMAND", foreground="sienna")
self.log_text.tag_configure("VENDOR", foreground="#8B008B") # Dark magenta
# Continue with existing scrollbar setup
self.log_scrollbar_y = ttk.Scrollbar( self.log_scrollbar_y = ttk.Scrollbar(
self.log_frame, orient="vertical", command=self.log_text.yview self.log_frame, orient="vertical", command=self.log_text.yview
) )
@ -459,7 +487,7 @@ class IPChangerApp:
1, weight=1 1, weight=1
) # Make the scan frame expandable ) # Make the scan frame expandable
# Network Tools section (moved from original layout) # Network Tools section (ping tool)
self.ping_frame = ttk.LabelFrame( self.ping_frame = ttk.LabelFrame(
self.tools_control_frame, text="Ping Tool", padding="5" self.tools_control_frame, text="Ping Tool", padding="5"
) )
@ -479,17 +507,27 @@ class IPChangerApp:
# Bind the FocusOut event to save the ping target # Bind the FocusOut event to save the ping target
self.ping_entry.bind("<FocusOut>", self.save_current_ping_target) self.ping_entry.bind("<FocusOut>", self.save_current_ping_target)
# Add last ping time display field
ttk.Label(self.ping_frame, text="Last RTT:").grid(
row=0, column=2, sticky="w", padx=5
)
self.last_ping_time = tk.StringVar(value="N/A")
ping_time_display = ttk.Entry(
self.ping_frame, textvariable=self.last_ping_time, width=8, state="readonly"
)
ping_time_display.grid(row=0, column=3, padx=5)
# Add continuous ping checkbox # Add continuous ping checkbox
self.continuous_ping_check = ttk.Checkbutton( self.continuous_ping_check = ttk.Checkbutton(
self.ping_frame, text="Continuous Ping", variable=self.continuous_ping self.ping_frame, text="Continuous Ping", variable=self.continuous_ping
) )
self.continuous_ping_check.grid(row=0, column=2, padx=5) self.continuous_ping_check.grid(row=0, column=4, padx=5)
ttk.Button(self.ping_frame, text="Ping", command=self.do_ping).grid( ttk.Button(self.ping_frame, text="Ping", command=self.do_ping).grid(
row=0, column=3, padx=5 row=0, column=5, padx=5
) )
ttk.Button(self.ping_frame, text="Stop", command=self.stop_ping).grid( ttk.Button(self.ping_frame, text="Stop", command=self.stop_ping).grid(
row=0, column=4, padx=5 row=0, column=6, padx=5
) )
# Network Scan section - make it expand both horizontally and vertically # Network Scan section - make it expand both horizontally and vertically
@ -586,29 +624,33 @@ class IPChangerApp:
# Replace Listbox with Treeview that has columns - make sure it expands # Replace Listbox with Treeview that has columns - make sure it expands
self.scan_results_tree = ttk.Treeview( self.scan_results_tree = ttk.Treeview(
self.results_frame, self.results_frame,
columns=("ip", "hostname", "mac", "vendor"), # Add vendor column columns=("ip", "delay", "hostname", "mac", "vendor"), # Added delay column
show="headings", show="headings",
height=10, height=10,
) )
# Define columns # Define columns
self.scan_results_tree.heading("ip", text="IP Address") self.scan_results_tree.heading("ip", text="IP Address")
self.scan_results_tree.heading(
"delay", text="Ping (ms)"
) # New column for ping delay
self.scan_results_tree.heading("hostname", text="Hostname") self.scan_results_tree.heading("hostname", text="Hostname")
self.scan_results_tree.heading("mac", text="MAC Address") self.scan_results_tree.heading("mac", text="MAC Address")
self.scan_results_tree.heading( self.scan_results_tree.heading("vendor", text="MAC Vendor")
"vendor", text="MAC Vendor"
) # Add vendor heading
# Set column widths # Set column widths with adjusted proportions
total_width = 650 # Base total width for the columns total_width = 650 # Base total width for the columns
self.scan_results_tree.column( self.scan_results_tree.column(
"ip", width=int(total_width * 0.18), anchor="w", stretch=True "ip", width=int(total_width * 0.15), anchor="w", stretch=True
) )
self.scan_results_tree.column( self.scan_results_tree.column(
"hostname", width=int(total_width * 0.30), anchor="w", stretch=True "delay", width=int(total_width * 0.08), anchor="center", stretch=True
) )
self.scan_results_tree.column( self.scan_results_tree.column(
"mac", width=int(total_width * 0.22), anchor="w", stretch=True "hostname", width=int(total_width * 0.27), anchor="w", stretch=True
)
self.scan_results_tree.column(
"mac", width=int(total_width * 0.20), anchor="w", stretch=True
) )
self.scan_results_tree.column( self.scan_results_tree.column(
"vendor", width=int(total_width * 0.30), anchor="w", stretch=True "vendor", width=int(total_width * 0.30), anchor="w", stretch=True
@ -627,8 +669,52 @@ class IPChangerApp:
def clear_log(self): def clear_log(self):
self.log_text.delete("1.0", tk.END) self.log_text.delete("1.0", tk.END)
def log_message(self, message: str): def log_message(self, message: str, tag: str = None):
self.log_text.insert(tk.END, f"{message}\n") """Enhanced log function with tags for color and formatting"""
# Try to automatically detect message type if tag not specified
if tag is None:
if "ERROR:" in message or "Error" in message or "failed" in message.lower():
tag = "ERROR"
elif (
"SUCCESS" in message
or "Successfully" in message
or "completed" in message
):
tag = "SUCCESS"
elif "WARNING" in message or "Warn" in message:
tag = "WARN"
elif "Host discovered:" in message:
tag = "DISCOVERY"
elif (
"Reply from" in message
or "ping" in message.lower()
or "Request timed out" in message
):
tag = "PING"
elif "Hostname:" in message:
tag = "HOSTNAME"
elif "MAC Address:" in message:
tag = "MAC"
elif "MAC Vendor:" in message or "Vendor:" in message:
tag = "VENDOR"
elif "Executing" in message and "command" in message.lower():
tag = "COMMAND"
elif "INFO:" in message:
tag = "INFO"
else:
tag = None
# Insert the timestamp and message
timestamp = time.strftime("%H:%M:%S", time.localtime())
self.log_text.insert(tk.END, f"[{timestamp}] ", "timestamp")
# Insert the message with appropriate tag
if tag:
self.log_text.insert(tk.END, f"{message}\n", tag)
else:
self.log_text.insert(tk.END, f"{message}\n")
# Scroll to see the latest message
self.log_text.see(tk.END) self.log_text.see(tk.END)
self.log_text.update_idletasks() self.log_text.update_idletasks()
@ -1017,6 +1103,7 @@ class IPChangerApp:
self.log_message("Stopping continuous ping...") self.log_message("Stopping continuous ping...")
self.ping_stop_event.set() self.ping_stop_event.set()
self.ping_running = False self.ping_running = False
self.last_ping_time.set("N/A") # Reset when stopped
def _execute_continuous_ping(self, target: str): def _execute_continuous_ping(self, target: str):
"""Execute continuous ping until stopped""" """Execute continuous ping until stopped"""
@ -1039,13 +1126,17 @@ class IPChangerApp:
for reply in response: for reply in response:
if reply.success: if reply.success:
rtt = reply.time_elapsed_ms rtt = reply.time_elapsed_ms
# Update the last ping time field
self.last_ping_time.set(f"{rtt:.1f} ms")
self.log_message( self.log_message(
f"Reply from {target}: time={rtt:.2f}ms (seq={count})" f"Reply from {target}: time={rtt:.2f}ms (seq={count})"
) )
break break
else: else:
self.last_ping_time.set("Timeout")
self.log_message(f"Request timed out (seq={count})") self.log_message(f"Request timed out (seq={count})")
except Exception as ping_error: except Exception as ping_error:
self.last_ping_time.set("Error")
self.log_message(f"Ping error: {str(ping_error)} (seq={count})") self.log_message(f"Ping error: {str(ping_error)} (seq={count})")
else: else:
# Fallback to socket # Fallback to socket
@ -1058,14 +1149,18 @@ class IPChangerApp:
s.close() s.close()
if result == 0: if result == 0:
# Update the last ping time field
self.last_ping_time.set(f"{elapsed_time:.1f} ms")
self.log_message( self.log_message(
f"Connected to {target}:80 in {elapsed_time:.2f}ms (seq={count})" f"Connected to {target}:80 in {elapsed_time:.2f}ms (seq={count})"
) )
else: else:
self.last_ping_time.set("Failed")
self.log_message( self.log_message(
f"Failed to connect to {target}:80 (seq={count})" f"Failed to connect to {target}:80 (seq={count})"
) )
except Exception as socket_error: except Exception as socket_error:
self.last_ping_time.set("Error")
self.log_message( self.log_message(
f"Connection error: {str(socket_error)} (seq={count})" f"Connection error: {str(socket_error)} (seq={count})"
) )
@ -1086,23 +1181,40 @@ class IPChangerApp:
self.log_message(f"Error in continuous ping: {str(e)}") self.log_message(f"Error in continuous ping: {str(e)}")
finally: finally:
self.ping_running = False self.ping_running = False
self.last_ping_time.set("N/A") # Reset on stop
def _execute_ping(self, target: str): def _execute_ping(self, target: str):
"""Execute a single ping and display the results""" """Execute a single ping and display the results"""
try: try:
self.log_message(f"Pinging {target} with 4 echo requests...") # Reset the last ping time to "Testing..."
self.last_ping_time.set("Testing...")
self.log_message(f"Pinging {target} with 4 echo requests...", "PING")
if PING_LIBRARY_AVAILABLE: if PING_LIBRARY_AVAILABLE:
# Use PythonPing library # Use PythonPing library
response = pythonping(target, count=4, timeout=1) response = pythonping(target, count=4, timeout=1)
# Display individual replies # Display individual replies with PING tag
total_rtt = 0
successful_pings = 0
for i, reply in enumerate(response): for i, reply in enumerate(response):
if reply.success: if reply.success:
rtt = reply.time_elapsed_ms rtt = reply.time_elapsed_ms
self.log_message(f"Reply from {target}: time={rtt:.2f}ms") total_rtt += rtt
successful_pings += 1
self.log_message(
f"Reply from {target}: time={rtt:.2f}ms", "PING"
)
# Update the last ping time with the most recent successful ping
self.last_ping_time.set(f"{rtt:.1f} ms")
else: else:
self.log_message(f"Request timed out (seq={i+1})") self.log_message(f"Request timed out (seq={i+1})", "PING")
# If all pings failed, update the last ping time appropriately
if successful_pings == 0:
self.last_ping_time.set("Timeout")
# Display statistics # Display statistics
success_count = sum(1 for r in response if r.success) success_count = sum(1 for r in response if r.success)
@ -1179,14 +1291,15 @@ class IPChangerApp:
vendor = self.get_mac_vendor(mac_address) vendor = self.get_mac_vendor(mac_address)
if hostname: if hostname:
self.log_message(f"Hostname: {hostname}") self.log_message(f"Hostname: {hostname}", "HOSTNAME")
if mac_address: if mac_address:
self.log_message(f"MAC Address: {mac_address}") self.log_message(f"MAC Address: {mac_address}", "MAC")
if vendor: if vendor:
self.log_message(f"MAC Vendor: {vendor}") self.log_message(f"MAC Vendor: {vendor}", "VENDOR")
except Exception as e: except Exception as e:
self.log_message(f"Error executing ping: {str(e)}") self.last_ping_time.set("Error")
self.log_message(f"Error executing ping: {str(e)}", "ERROR")
def start_scan(self): def start_scan(self):
"""Start network scanning between IP range""" """Start network scanning between IP range"""
@ -1291,7 +1404,7 @@ class IPChangerApp:
self.scan_running = False self.scan_running = False
def _scan_worker(self): def _scan_worker(self):
"""Worker thread function to scan IPs from the queue - now only performs ping scan""" """Worker thread function to scan IPs from the queue - now with ping time recording"""
while not self.scan_stop_event.is_set(): while not self.scan_stop_event.is_set():
try: try:
# Get next IP from queue with timeout # Get next IP from queue with timeout
@ -1301,18 +1414,24 @@ class IPChangerApp:
# No more IPs to scan # No more IPs to scan
break break
# Ping the IP # Ping the IP and measure the time
is_active = self._ping_host(str(ip_address)) ping_time = 0
ip_str = str(ip_address)
is_active, ping_delay = self._ping_host_with_time(ip_str)
# If active, add to results - but don't get host info yet # If active, add to results - but don't get host info yet
if is_active: if is_active:
ip_str = str(ip_address) self.log_message(
self.log_message(f"Host discovered: {ip_str}") f"Host discovered: {ip_str} ({ping_delay:.1f}ms)", "DISCOVERY"
)
self.scan_results.append(ip_str) self.scan_results.append(ip_str)
# Add to results tree with placeholder values # Add to results tree with placeholder values and ping time
self.master.after( self.master.after(
0, lambda ip=ip_str: self.add_scan_result(ip, "", "", "") 0,
lambda ip=ip_str, delay=ping_delay: self.add_scan_result(
ip, delay, "", "", ""
),
) )
# Update progress # Update progress
@ -1338,7 +1457,7 @@ class IPChangerApp:
"""Perform the actual host information gathering""" """Perform the actual host information gathering"""
try: try:
self.log_message( self.log_message(
f"Gathering information for {len(self.scan_results)} hosts..." f"Gathering information for {len(self.scan_results)} hosts...", "INFO"
) )
# Reset progress bar for this operation # Reset progress bar for this operation
@ -1349,7 +1468,7 @@ class IPChangerApp:
# Get information for each host # Get information for each host
for i, ip in enumerate(self.scan_results): for i, ip in enumerate(self.scan_results):
if self.scan_stop_event.is_set(): if self.scan_stop_event.is_set():
self.log_message("Host information gathering stopped.") self.log_message("Host information gathering stopped.", "WARN")
break break
self.log_message( self.log_message(
@ -1364,21 +1483,20 @@ class IPChangerApp:
# Look up vendor information if we have a MAC address # Look up vendor information if we have a MAC address
if mac_address and MAC_LOOKUP_AVAILABLE: if mac_address and MAC_LOOKUP_AVAILABLE:
vendor = self.get_mac_vendor(mac_address) vendor = self.get_mac_vendor(mac_address)
if vendor:
self.log_message(f" MAC Vendor: {vendor}")
else:
self.log_message(f" MAC Vendor: Unknown")
# Log what we found # Log the results in a cleaner format
result_parts = []
if hostname: if hostname:
self.log_message(f" Hostname: {hostname}") result_parts.append(f"Hostname: {hostname}")
else:
self.log_message(f" Hostname: Not resolved")
if mac_address: if mac_address:
self.log_message(f" MAC Address: {mac_address}") result_parts.append(f"MAC: {mac_address}")
if vendor:
result_parts.append(f"Vendor: {vendor}")
if result_parts:
self.log_message(f" {ip}" + " | ".join(result_parts))
else: else:
self.log_message(f" MAC Address: Not found") self.log_message(f" {ip} → No additional information found")
# Update the tree with vendor information # Update the tree with vendor information
self.update_host_in_tree(ip, hostname, mac_address, vendor) self.update_host_in_tree(ip, hostname, mac_address, vendor)
@ -1386,10 +1504,10 @@ class IPChangerApp:
# Update progress # Update progress
self.scan_progress_var.set(i + 1) self.scan_progress_var.set(i + 1)
self.log_message("Host information gathering completed.") self.log_message("Host information gathering completed.", "SUCCESS")
except Exception as e: except Exception as e:
self.log_message(f"Error gathering host information: {str(e)}") self.log_message(f"Error gathering host information: {str(e)}", "ERROR")
def update_host_in_tree(self, ip, hostname, mac, vendor=""): def update_host_in_tree(self, ip, hostname, mac, vendor=""):
"""Update an existing host in the treeview with obtained information""" """Update an existing host in the treeview with obtained information"""
@ -1398,46 +1516,66 @@ class IPChangerApp:
for item_id in self.scan_results_tree.get_children(): for item_id in self.scan_results_tree.get_children():
values = self.scan_results_tree.item(item_id, "values") values = self.scan_results_tree.item(item_id, "values")
if values and values[0] == ip: if values and values[0] == ip:
# Update the values # Get the current delay value (to preserve it)
delay = values[1] if len(values) > 1 else ""
# Update the values, preserving the delay value
hostname_str = hostname if hostname else "Not resolved" hostname_str = hostname if hostname else "Not resolved"
mac_str = mac if mac else "Not found" mac_str = (
mac.upper() if mac else "Not found"
) # Ensure uppercase MAC
vendor_str = vendor if vendor else "Unknown" vendor_str = vendor if vendor else "Unknown"
self.scan_results_tree.item( self.scan_results_tree.item(
item_id, values=(ip, hostname_str, mac_str, vendor_str) item_id, values=(ip, delay, hostname_str, mac_str, vendor_str)
) )
break break
except Exception as e: except Exception as e:
self.log_message(f"Error updating host in treeview: {str(e)}") self.log_message(f"Error updating host in treeview: {str(e)}")
def add_scan_result(self, ip, hostname, mac, vendor=""): def add_scan_result(self, ip, delay=0, hostname="", mac="", vendor=""):
"""Helper method to add scan result to the treeview""" """Helper method to add scan result to the treeview"""
try: try:
# Insert the item with all values, even if some are empty # Insert the item with all values
hostname = hostname if hostname else "Not resolved" hostname = hostname if hostname else "Not resolved"
mac = mac if mac else "Not found" mac = mac if mac else "Not found"
vendor = vendor if vendor else "Unknown" vendor = vendor if vendor else "Unknown"
delay_str = f"{delay:.1f}" if delay > 0 else ""
self.scan_results_tree.insert("", "end", values=(ip, hostname, mac, vendor)) self.scan_results_tree.insert(
"", "end", values=(ip, delay_str, hostname, mac, vendor)
)
except Exception as e: except Exception as e:
self.log_message(f"Error adding scan result to UI: {str(e)}") self.log_message(f"Error adding scan result to UI: {str(e)}")
def _ping_host(self, host: str) -> bool: def _ping_host_with_time(self, host: str) -> tuple[bool, float]:
"""Ping a host to check if it's active, returns True if host responds""" """Ping a host to check if it's active, returns (success, delay_in_ms)"""
try: try:
if PING_LIBRARY_AVAILABLE: if PING_LIBRARY_AVAILABLE:
# Use PythonPing with shorter timeout for scanning # Use PythonPing with shorter timeout for scanning
start_time = time.time()
response = pythonping(host, count=1, timeout=0.5) response = pythonping(host, count=1, timeout=0.5)
return response.success() ping_time = 0
if response.success():
# Get the actual ping time from the response
for reply in response:
if reply.success:
ping_time = reply.time_elapsed_ms
break
return True, ping_time
return False, 0
else: else:
# Fallback using socket # Fallback using socket with timing
start_time = time.time()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5) s.settimeout(0.5)
result = s.connect_ex((host, 80)) # Try common port result = s.connect_ex((host, 80)) # Try common port
ping_time = (time.time() - start_time) * 1000 # ms
s.close() s.close()
return result == 0 return result == 0, ping_time if result == 0 else 0
except: except:
return False return False, 0
def set_static_ip(self): def set_static_ip(self):
interface = self.if_var.get() interface = self.if_var.get()
@ -1553,11 +1691,11 @@ class IPChangerApp:
self.show_error("Failed to elevate privileges") self.show_error("Failed to elevate privileges")
def show_error(self, message: str): def show_error(self, message: str):
self.log_message(f"ERROR: {message}") self.log_message(f"ERROR: {message}", "ERROR")
messagebox.showerror("Error", message) messagebox.showerror("Error", message)
def show_info(self, message: str): def show_info(self, message: str):
self.log_message(f"INFO: {message}") self.log_message(f"INFO: {message}", "INFO")
messagebox.showinfo("Information", message) messagebox.showinfo("Information", message)
# Add new methods for subnet mask conversion # Add new methods for subnet mask conversion
@ -1782,22 +1920,17 @@ class IPChangerApp:
def get_hostname(self, ip_address): def get_hostname(self, ip_address):
"""Try to resolve hostname for an IP address with better error handling""" """Try to resolve hostname for an IP address with better error handling"""
try: try:
self.log_message(f"Resolving hostname for {ip_address}...") # Silently try to resolve without logging the attempt
hostname, _, _ = socket.gethostbyaddr(ip_address) hostname, _, _ = socket.gethostbyaddr(ip_address)
return hostname return hostname
except socket.herror as e: except socket.herror:
self.log_message(f"Hostname resolution error for {ip_address}: {e}") # Don't log the error details - just return empty
return "" return ""
except socket.gaierror as e: except socket.gaierror:
self.log_message(f"Address-related error for {ip_address}: {e}")
return "" return ""
except socket.timeout: except socket.timeout:
self.log_message(f"Timeout resolving hostname for {ip_address}")
return "" return ""
except Exception as e: except Exception:
self.log_message(
f"Unknown error resolving hostname for {ip_address}: {str(e)}"
)
return "" return ""
def get_mac_address(self, ip_address): def get_mac_address(self, ip_address):
@ -1813,10 +1946,7 @@ class IPChangerApp:
) )
if result.returncode == 0 and result.stdout: if result.returncode == 0 and result.stdout:
# Log the raw output for debugging # Don't log the raw ARP output anymore
self.log_message(
f"ARP output for {ip_address}: {result.stdout.strip()}"
)
# Parse the output to find the MAC address # Parse the output to find the MAC address
# First try the typical format with hyphens or colons # First try the typical format with hyphens or colons
@ -1826,7 +1956,8 @@ class IPChangerApp:
re.IGNORECASE, re.IGNORECASE,
) )
if mac_match: if mac_match:
return mac_match.group(0) # Convert to uppercase for consistent display
return mac_match.group(0).upper()
# Try an alternative format with spaces (Windows format) # Try an alternative format with spaces (Windows format)
mac_match = re.search( mac_match = re.search(
@ -1835,7 +1966,8 @@ class IPChangerApp:
re.IGNORECASE, re.IGNORECASE,
) )
if mac_match: if mac_match:
return mac_match.group(0) # Convert to uppercase for consistent display
return mac_match.group(0).upper()
# Try yet another approach - look for any string of hex digits with separators # Try yet another approach - look for any string of hex digits with separators
lines = result.stdout.strip().split("\n") lines = result.stdout.strip().split("\n")
@ -1850,15 +1982,14 @@ class IPChangerApp:
parts[1], parts[1],
re.IGNORECASE, re.IGNORECASE,
): ):
return parts[1] # Convert to uppercase for consistent display
return parts[1].upper()
self.log_message(f"No MAC address found for {ip_address}") # Only return empty string if MAC not found - don't log the failure
return "" return ""
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
self.log_message(f"Timeout running ARP command for {ip_address}")
return "" return ""
except Exception as e: except Exception:
self.log_message(f"Error getting MAC address for {ip_address}: {str(e)}")
return "" return ""
def get_mac_vendor(self, mac_address): def get_mac_vendor(self, mac_address):