Good morning,
I have a new question, where I am trying to put together an app for my less tech savy folks. I have it running, and things seem to work fine, except for devices that respond under 1ms. It says they are online but are N/A for times. Is there anyway to fix that?
import subprocess
from time import time, sleep
import re
from datetime import datetime
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
import threading
import socket
#
DEBUG = True # Print messages to terminal
IPS = ["10.1.1.1", "10.10.254.1", "8.8.8.8"] # List of IPs to ping
OUTAGE_DURATION_SEC = 60 # Shortest detectable outage
ONLINE = "online"
OUTAGE = "outage"
UNREACHABLE = "network unreachable"
class PingApp:
def __init__(self, root):
self.root = root
self.root.title("Ping Monitor")
self.root.geometry("1024x768")
# Set default background color
self.root.configure(bg="#f0f0f0") # Light gray background
# Add a ScrolledText area with customized background and font color
self.text_area = ScrolledText(
root, wrap=tk.WORD, font=("Consolas", 16), bg="#ffffff", fg="#000000"
)
self.text_area.pack(fill=tk.BOTH, expand=True)
self.stop_event = threading.Event()
# Add a start and stop button with consistent background color
button_frame = tk.Frame(root, bg="#f0f0f0")
button_frame.pack(fill=tk.X)
start_button = tk.Button(button_frame, text="Start", command=self.start_monitoring, bg="#d0eaff", fg="#000000")
start_button.pack(side=tk.LEFT, padx=10, pady=5)
stop_button = tk.Button(button_frame, text="Stop", command=self.stop_monitoring, bg="#ffcccc", fg="#000000")
stop_button.pack(side=tk.LEFT, padx=10, pady=5)
# Add a button to show the client's IP in a pop-up
client_ip_button = tk.Button(button_frame, text="Show Client IP", command=self.show_client_ip_popup, bg="#ccffcc", fg="#000000")
client_ip_button.pack(side=tk.LEFT, padx=10, pady=5)
# User-defined IP section
self.ip_entries = []
ip_frame = tk.Frame(root, bg="#f0f0f0")
ip_frame.pack(fill=tk.X, padx=10, pady=10)
tk.Label(ip_frame, text="Enter IPs to Monitor (4):", bg="#f0f0f0", fg="#000000").pack(anchor=tk.W)
for i in range(4):
entry = tk.Entry(ip_frame, width=30, bg="#ffffff", fg="#000000")
entry.pack(padx=5, pady=2)
self.ip_entries.append(entry)
submit_button = tk.Button(ip_frame, text="Submit IPs", command=self.update_ips, bg="#d9d9d9", fg="#000000")
submit_button.pack(pady=5)
self.ips = [] # Placeholder for user-defined IPs
def log(self, message):
"""Log messages to the GUI and optionally to the console."""
self.text_area.insert(tk.END, message + "\n")
self.text_area.see(tk.END)
if DEBUG:
print(message)
def update_ips(self):
"""Update the list of IPs based on user input."""
self.ips = []
for entry in self.ip_entries:
ip = entry.get().strip()
if self.validate_ip(ip):
self.ips.append(ip)
else:
self.log(f"Invalid IP address: {ip}")
if len(self.ips) == 4:
self.log(f"Updated IP list: {', '.join(self.ips)}")
else:
self.log("Please provide exactly 4 valid IP addresses.")
@staticmethod
def validate_ip(ip):
"""Validate if the string is a valid IP address."""
pattern = re.compile(r"^(?:\d{1,3}\.){3}\d{1,3}$")
return bool(pattern.match(ip)) and all(0 <= int(octet) <= 255 for octet in ip.split("."))
def show_client_ip_popup(self):
"""Display the client's IP address in a pop-up window."""
ip_address = self.get_client_ip()
popup = tk.Toplevel(self.root)
popup.title("Client IP Address")
popup.geometry("300x100")
# Add a label to show the IP address with a light background
ip_label = tk.Label(popup, text=f"Client IP: {ip_address}", font=("Arial", 12), bg="#f7f7f7", fg="#000000")
ip_label.pack(pady=10)
# Add a close button with a consistent style
close_button = tk.Button(popup, text="Close", command=popup.destroy, font=("Arial", 10), bg="#d9d9d9", fg="#000000")
close_button.pack(pady=5)
@staticmethod
def get_client_ip():
"""Retrieve the local IP address of the client."""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0)
try:
s.connect(('10.254.254.254', 1)) # Arbitrary external IP
ip = s.getsockname()[0]
except Exception:
ip = '127.0.0.1' # Fallback to localhost
finally:
s.close()
return ip
def start_monitoring(self):
if len(self.ips) != 4:
self.log("Error: Please ensure 4 valid IPs are provided before starting.")
return
self.stop_event.clear()
self.monitor_thread = threading.Thread(target=self.monitor_ips, daemon=True)
self.monitor_thread.start()
def stop_monitoring(self):
self.stop_event.set()
def monitor_ips(self):
poll_sec = OUTAGE_DURATION_SEC / 2.0
while not self.stop_event.is_set():
for ip in self.ips:
ping_msg = get_ping_msg(ip)
network_status = get_network_status(ping_msg)
ping_time = get_ping_time(ping_msg)
timestamp = time()
date_str = get_date_str(timestamp)
time_str = get_time_str(timestamp)
log_message = f"{date_str} {time_str} | IP: {ip} | Status: {network_status} | Time: {ping_time if ping_time else 'N/A'} ms"
self.log(log_message)
sleep(poll_sec)
def get_filename():
timestamp = time()
date_str = get_date_str(timestamp)
filename = f"{date_str}.csv"
return filename
def get_ping_msg(ip):
ping_process = subprocess.Popen(
["ping", "-c", "1", ip], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
pingp = str(ping_process.stdout.read())
return pingp
def get_network_status(ping_msg):
if re.search(r"100.0% packet loss|100% packet loss", ping_msg) is not None:
return OUTAGE
elif re.search(r"Network is unreachable", ping_msg) is not None:
return UNREACHABLE
else:
return ONLINE
def get_ping_time(ping_msg):
match = re.search(r"/\d{2,4}\.\d{3}/", ping_msg)
if match is not None:
ping_time = float(match.group(0).replace("/", ""))
else:
ping_time = None
return ping_time
def get_date_str(timestamp):
dt = datetime.fromtimestamp(timestamp)
return dt.strftime("%Y-%m-%d")
def get_time_str(timestamp):
dt = datetime.fromtimestamp(timestamp)
return dt.strftime("%H:%M:%S")
if __name__ == "__main__":
root = tk.Tk()
app = PingApp(root)
root.mainloop()
It doesnt bother me, but for my two test users, its their biggest critique. Thanks for any help you can give.