diff --git a/logs/mesh_network_analyzer.py b/logs/mesh_network_analyzer.py new file mode 100644 index 0000000..0de4d3b --- /dev/null +++ b/logs/mesh_network_analyzer.py @@ -0,0 +1,560 @@ +import os +import re +from datetime import datetime +from collections import Counter, defaultdict +import json +import platform +import subprocess + +def parse_log_file(file_path): + with open(file_path, 'r') as file: + lines = file.readlines() + + log_data = { + 'command_counts': Counter(), + 'message_types': Counter(), + 'unique_users': set(), + 'warnings': [], + 'errors': [], + 'hourly_activity': defaultdict(int), + 'bbs_messages': 0, + 'total_messages': 0, + 'gps_coordinates': defaultdict(list), + 'command_timestamps': [], + 'message_timestamps': [], + } + + for line in lines: + timestamp_match = re.match(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),\d+', line) + if timestamp_match: + timestamp = datetime.strptime(timestamp_match.group(1), '%Y-%m-%d %H:%M:%S') + log_data['hourly_activity'][timestamp.strftime('%Y-%m-%d %H:00:00')] += 1 + + if 'Bot detected Commands' in line: + command = re.search(r"'cmd': '(\w+)'", line) + if command: + cmd = command.group(1) + log_data['command_counts'][cmd] += 1 + log_data['command_timestamps'].append((timestamp.isoformat(), cmd)) + + if 'Sending DM:' in line or 'Sending Multi-Chunk DM:' in line: + log_data['message_types']['Outgoing DM'] += 1 + log_data['total_messages'] += 1 + log_data['message_timestamps'].append((timestamp.isoformat(), 'Outgoing DM')) + + if 'Received DM:' in line: + log_data['message_types']['Incoming DM'] += 1 + log_data['total_messages'] += 1 + log_data['message_timestamps'].append((timestamp.isoformat(), 'Incoming DM')) + + user_match = re.search(r'From: (\w+)', line) + if user_match: + log_data['unique_users'].add(user_match.group(1)) + + if '| WARNING |' in line: + log_data['warnings'].append(line.strip()) + + if '| ERROR |' in line: + log_data['errors'].append(line.strip()) + + bbs_match = re.search(r'📡BBSdb has (\d+) messages', line) + if bbs_match: + log_data['bbs_messages'] = int(bbs_match.group(1)) + + gps_match = re.search(r'location data for (\d+) is ([-\d.]+),([-\d.]+)', line) + if gps_match: + node_id, lat, lon = gps_match.groups() + log_data['gps_coordinates'][node_id].append((float(lat), float(lon))) + + log_data['unique_users'] = list(log_data['unique_users']) + return log_data + +def get_system_info(): + def get_command_output(command): + try: + return subprocess.check_output(command, shell=True).decode('utf-8').strip() + except subprocess.CalledProcessError: + return "N/A" + + if platform.system() == "Linux": + uptime = get_command_output("uptime -p") + memory_total = get_command_output("free -m | awk '/Mem:/ {print $2}'") + memory_available = get_command_output("free -m | awk '/Mem:/ {print $7}'") + disk_total = get_command_output("df -h / | awk 'NR==2 {print $2}'") + disk_free = get_command_output("df -h / | awk 'NR==2 {print $4}'") + elif platform.system() == "Darwin": # macOS + uptime = get_command_output("uptime | awk '{print $3,$4,$5}'") + memory_total = get_command_output("sysctl -n hw.memsize | awk '{print $0/1024/1024}'") + memory_available = "N/A" # Not easily available on macOS without additional tools + disk_total = get_command_output("df -h / | awk 'NR==2 {print $2}'") + disk_free = get_command_output("df -h / | awk 'NR==2 {print $4}'") + else: + return { + 'uptime': "N/A", + 'memory_total': "N/A", + 'memory_available': "N/A", + 'disk_total': "N/A", + 'disk_free': "N/A", + } + + return { + 'uptime': uptime, + 'memory_total': f"{memory_total} MB", + 'memory_available': f"{memory_available} MB" if memory_available != "N/A" else "N/A", + 'disk_total': disk_total, + 'disk_free': disk_free, + } + +def generate_main_html(log_data, system_info): + html_template = """ + + + + + + Meshbot (BBS) Network Statistics + + + + + + + +
Meshbot (BBS) Network Statistics
+ +
+
+
Node Locations
+
+
+
+
Network Activity
+
+ +
+
+
+
Command Usage
+
+ +
+
+
+
Message Types
+
+ +
+
+
+
Message Counts
+
+ +
+
+
+
Recent Commands
+
+
    + ${command_timestamps} +
+
+
+
+
Recent Messages
+
+
    + ${message_timestamps} +
+
+
+
+
Unique Users
+ +
+
+
Warnings
+ +
+
+
Errors
+ +
+
+
+ +
+ + + + """ + + from string import Template + template = Template(html_template) + return template.safe_substitute( + date=datetime.now().strftime('%Y_%m_%d'), + command_data=json.dumps(log_data['command_counts']), + message_data=json.dumps(log_data['message_types']), + activity_data=json.dumps(log_data['hourly_activity']), + bbs_messages=log_data['bbs_messages'], + total_messages=log_data['total_messages'], + gps_coordinates=json.dumps(log_data['gps_coordinates']), + unique_users='\n'.join(f'
  • {user}
  • ' for user in log_data['unique_users']), + warnings='\n'.join(f'
  • {warning}
  • ' for warning in log_data['warnings']), + errors='\n'.join(f'
  • {error}
  • ' for error in log_data['errors']), + command_timestamps='\n'.join(f'
  • {timestamp}: {cmd}
  • ' for timestamp, cmd in reversed(log_data['command_timestamps'][-50:])), + message_timestamps='\n'.join(f'
  • {timestamp}: {msg_type}
  • ' for timestamp, msg_type in reversed(log_data['message_timestamps'][-50:])) + ) + +def generate_network_map_html(log_data): + html_template = """ + + + + + + Network Map + + + + + +
    + + + + """ + + from string import Template + template = Template(html_template) + return template.safe_substitute(gps_coordinates=json.dumps(log_data['gps_coordinates'])) + +def generate_hosts_html(system_info): + html_template = """ + + + + + + Host Information + + + +

    Host Information

    + + + + + + + +
    MetricValue
    Uptime${uptime}
    Total Memory${memory_total}
    Available Memory${memory_available}
    Total Disk Space${disk_total}
    Free Disk Space${disk_free}
    + + + """ + + from string import Template + template = Template(html_template) + return template.safe_substitute(system_info) + +def main(): + log_dir = '/opt/meshing-around/logs' + today = datetime.now().strftime('%Y_%m_%d') + log_file = f'meshbot{today}.log' + log_path = os.path.join(log_dir, log_file) + + log_data = parse_log_file(log_path) + system_info = get_system_info() + + main_html = generate_main_html(log_data, system_info) + network_map_html = generate_network_map_html(log_data) + hosts_html = generate_hosts_html(system_info) + + output_dir = '/var/www/html' + index_path = os.path.join(output_dir, 'index.html') + + try: + # Create backup of existing index.html if it exists + if os.path.exists(index_path): + backup_path = os.path.join(output_dir, f'index_backup_{today}.html') + os.rename(index_path, backup_path) + print(f"Existing index.html backed up to {backup_path}") + + # Write main HTML to index.html + with open(index_path, 'w') as f: + f.write(main_html) + print(f"Main dashboard written to {index_path}") + + # Write other HTML files + with open(os.path.join(output_dir, f'network_map_{today}.html'), 'w') as f: + f.write(network_map_html) + + with open(os.path.join(output_dir, f'hosts_{today}.html'), 'w') as f: + f.write(hosts_html) + + print(f"HTML reports generated for {today} in {output_dir}") + + except PermissionError: + print("Error: Permission denied. Please run the script with appropriate permissions (e.g., using sudo).") + except Exception as e: + print(f"An error occurred while writing the output: {str(e)}") + +if __name__ == "__main__": + main()