diff --git a/modules/web_viewer/app.py b/modules/web_viewer/app.py index bf493db..e1cdab5 100644 --- a/modules/web_viewer/app.py +++ b/modules/web_viewer/app.py @@ -8,6 +8,7 @@ import configparser import json import logging import os +import re import sqlite3 import subprocess import sys @@ -68,6 +69,15 @@ def _apply_werkzeug_websocket_fix() -> None: _apply_werkzeug_websocket_fix() +# colorlog and other handlers write ANSI SGR sequences; strip for web /logs display +_ANSI_ESCAPE_RE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + + +def _strip_ansi_codes(text: str) -> str: + """Remove ANSI color and reset codes from log lines for SocketIO web clients.""" + return _ANSI_ESCAPE_RE.sub("", text) + + # Add the project root to the path so we can import bot components project_root = os.path.join(os.path.dirname(__file__), '..', '..') sys.path.insert(0, project_root) @@ -3638,7 +3648,7 @@ class BotDataViewer: with open(log_file, encoding='utf-8', errors='replace') as _fh: recent_lines = _fh.readlines()[-200:] for line in recent_lines: - emit('log_line', {'line': line.rstrip()}) + emit('log_line', {'line': _strip_ansi_codes(line.rstrip())}) except Exception as e: self.logger.debug(f"Error reading log history: {e}") except Exception as e: @@ -3753,7 +3763,9 @@ class BotDataViewer: if info.get('subscribed_logs', False) ] if subscribed: - self.socketio.emit('log_line', {'line': line.rstrip()}, room=None) + self.socketio.emit( + 'log_line', {'line': _strip_ansi_codes(line.rstrip())}, room=None + ) except Exception as e: self.logger.error(f"Error broadcasting log line: {e}") diff --git a/modules/web_viewer/templates/base.html b/modules/web_viewer/templates/base.html index f3270b2..60de20e 100644 --- a/modules/web_viewer/templates/base.html +++ b/modules/web_viewer/templates/base.html @@ -310,6 +310,40 @@ color: var(--text-color) !important; } + /* Dark mode dropdown menus */ + [data-theme="dark"] .dropdown-menu { + background-color: var(--card-bg); + border-color: var(--border-color); + box-shadow: 0 0.5rem 1rem var(--shadow-color); + } + + [data-theme="dark"] .dropdown-item { + color: var(--text-color); + } + + [data-theme="dark"] .dropdown-item:hover, + [data-theme="dark"] .dropdown-item:focus { + background-color: var(--bg-tertiary); + color: var(--text-color); + } + + [data-theme="dark"] .dropdown-item.text-danger { + color: #f5c2c7; + } + + [data-theme="dark"] .dropdown-item.text-danger:hover, + [data-theme="dark"] .dropdown-item.text-danger:focus { + color: #f8d7da; + } + + [data-theme="dark"] .dropdown-header { + color: var(--text-muted); + } + + [data-theme="dark"] .dropdown-divider { + border-top-color: var(--border-color); + } + /* Dark mode accordion styling */ [data-theme="dark"] .accordion-item { background-color: var(--card-bg); diff --git a/modules/web_viewer/templates/contacts.html b/modules/web_viewer/templates/contacts.html index a69bb25..49e150f 100644 --- a/modules/web_viewer/templates/contacts.html +++ b/modules/web_viewer/templates/contacts.html @@ -2,6 +2,21 @@ {% block title %}Contacts - MeshCore Bot Data Viewer{% endblock %} +{% block extra_css %} + +{% endblock %} + {% block content %}