diff --git a/etc/mna.py b/etc/mna.py
new file mode 100644
index 0000000..a148e47
--- /dev/null
+++ b/etc/mna.py
@@ -0,0 +1,824 @@
+# -*- coding: utf-8 -*-
+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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Network Activity
+
+
+
+
+
+
+
+
+ Recent Commands
+
+
+ ${command_timestamps}
+
+
+
+
+ Recent Messages
+
+
+ ${message_timestamps}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+ 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
+
+ | Metric | Value |
+ | 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()
\ No newline at end of file