Add dark mode support across web viewer templates.

This commit is contained in:
agessaman committed 2025-12-06 12:35:58 -08:00
1 parent ff1adf719e
commit 183976e899
4 files changed
+380 -39

No files matched your search

+238 -10
View File
@@ -5,6 +5,16 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}MeshCore Bot Data Viewer{% endblock %}</title>
<!-- Apply theme immediately to prevent flash -->
<script>
(function() {
const storedTheme = localStorage.getItem('theme');
const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const theme = storedTheme || systemPreference;
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<!-- Bootstrap 5.1.3 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
@@ -13,6 +23,45 @@
<!-- Custom styles -->
<style>
/* CSS Variables for Light Mode (Default) */
:root {
--bg-color: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
--text-color: #212529;
--text-muted: #6c757d;
--border-color: #dee2e6;
--card-bg: #ffffff;
--card-header-bg: #f8f9fa;
--footer-bg: #f8f9fa;
--log-entry-bg: #f8f9fa;
--connection-info-bg: #e9ecef;
--shadow-color: rgba(0, 0, 0, 0.1);
}
/* CSS Variables for Dark Mode */
[data-theme="dark"] {
--bg-color: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3d3d3d;
--text-color: #e9ecef;
--text-muted: #adb5bd;
--border-color: #495057;
--card-bg: #2d2d2d;
--card-header-bg: #3d3d3d;
--footer-bg: #2d2d2d;
--log-entry-bg: #2d2d2d;
--connection-info-bg: #3d3d3d;
--shadow-color: rgba(0, 0, 0, 0.3);
}
/* Apply theme variables */
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.navbar-brand {
font-weight: bold;
color: #007bff !important;
@@ -35,8 +84,9 @@
margin-bottom: 5px;
padding: 8px;
border-left: 3px solid #007bff;
background-color: #f8f9fa;
background-color: var(--log-entry-bg);
border-radius: 0 5px 5px 0;
color: var(--text-color);
}
.command-entry {
@@ -48,21 +98,29 @@
}
.connection-info {
background-color: #e9ecef;
background-color: var(--connection-info-bg);
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
color: var(--text-color);
}
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-shadow: 0 2px 4px var(--shadow-color);
border: none;
background-color: var(--card-bg);
color: var(--text-color);
}
.card-header {
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
background-color: var(--card-header-bg);
border-bottom: 1px solid var(--border-color);
font-weight: 600;
color: var(--text-color);
}
.card-body {
color: var(--text-color);
}
.btn-group .btn {
@@ -73,22 +131,36 @@
border-radius: 8px;
overflow: hidden;
}
/* Dark mode table styles */
[data-theme="dark"] .table {
color: var(--text-color);
}
[data-theme="dark"] .table-striped > tbody > tr:nth-of-type(odd) > td {
background-color: var(--bg-tertiary);
}
[data-theme="dark"] .table-hover > tbody > tr:hover > td {
background-color: var(--bg-tertiary);
}
.badge {
font-size: 0.8em;
}
.footer {
background-color: #f8f9fa;
background-color: var(--footer-bg);
padding: 20px 0;
margin-top: 50px;
border-top: 1px solid #dee2e6;
border-top: 1px solid var(--border-color);
color: var(--text-color);
}
.loading {
text-align: center;
padding: 20px;
color: #6c757d;
color: var(--text-muted);
}
.error {
@@ -99,6 +171,12 @@
border-radius: 5px;
margin: 10px 0;
}
[data-theme="dark"] .error {
color: #f5c6cb;
background-color: #721c24;
border-color: #842029;
}
.success {
color: #155724;
@@ -108,6 +186,84 @@
border-radius: 5px;
margin: 10px 0;
}
[data-theme="dark"] .success {
color: #d1e7dd;
background-color: #0f5132;
border-color: #146c43;
}
/* Dark mode toggle button */
.dark-mode-toggle {
background: none;
border: none;
color: rgba(255, 255, 255, 0.85);
font-size: 1.2rem;
padding: 0.5rem 0.75rem;
cursor: pointer;
border-radius: 0.375rem;
transition: background-color 0.2s ease, color 0.2s ease;
}
.dark-mode-toggle:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.dark-mode-toggle:focus {
outline: none;
box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.25);
}
/* Dark mode form controls */
[data-theme="dark"] .form-control,
[data-theme="dark"] .form-select {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .form-control:focus,
[data-theme="dark"] .form-select:focus {
background-color: var(--bg-tertiary);
border-color: #007bff;
color: var(--text-color);
}
/* Dark mode text muted */
[data-theme="dark"] .text-muted {
color: var(--text-muted) !important;
}
/* Dark mode borders */
[data-theme="dark"] .border {
border-color: var(--border-color) !important;
}
/* Dark mode alerts */
[data-theme="dark"] .alert-info {
background-color: #084298;
border-color: #0a58ca;
color: #cfe2ff;
}
[data-theme="dark"] .alert-warning {
background-color: #664d03;
border-color: #997404;
color: #fffbeb;
}
[data-theme="dark"] .alert-danger {
background-color: #842029;
border-color: #b02a37;
color: #f8d7da;
}
[data-theme="dark"] .alert-success {
background-color: #0f5132;
border-color: #146c43;
color: #d1e7dd;
}
</style>
{% block extra_css %}{% endblock %}
@@ -167,8 +323,13 @@
</li>
</ul>
<!-- Connection Status -->
<div class="navbar-nav">
<!-- Dark Mode Toggle and Connection Status -->
<div class="navbar-nav d-flex align-items-center">
<div class="nav-item me-3">
<button class="dark-mode-toggle" id="dark-mode-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">
<i class="fas fa-moon" id="dark-mode-icon"></i>
</button>
</div>
<div class="nav-item">
<span class="navbar-text">
<span class="status-indicator" id="connection-status"></span>
@@ -340,8 +501,75 @@
}
}
// Dark Mode Management
class DarkModeManager {
constructor() {
this.theme = this.getStoredTheme() || this.getSystemPreference();
this.applyTheme();
this.setupToggle();
}
getStoredTheme() {
return localStorage.getItem('theme');
}
getSystemPreference() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
setTheme(theme) {
this.theme = theme;
localStorage.setItem('theme', theme);
this.applyTheme();
}
applyTheme() {
const root = document.documentElement;
if (this.theme === 'dark') {
root.setAttribute('data-theme', 'dark');
const icon = document.getElementById('dark-mode-icon');
if (icon) {
icon.classList.remove('fa-moon');
icon.classList.add('fa-sun');
}
} else {
root.setAttribute('data-theme', 'light');
const icon = document.getElementById('dark-mode-icon');
if (icon) {
icon.classList.remove('fa-sun');
icon.classList.add('fa-moon');
}
}
}
toggleTheme() {
const newTheme = this.theme === 'dark' ? 'light' : 'dark';
this.setTheme(newTheme);
}
setupToggle() {
const toggle = document.getElementById('dark-mode-toggle');
if (toggle) {
toggle.addEventListener('click', () => {
this.toggleTheme();
});
}
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
// Only apply system preference if user hasn't set a preference
if (!this.getStoredTheme()) {
this.setTheme(e.matches ? 'dark' : 'light');
}
});
}
}
// Initialize connection manager when page loads
document.addEventListener('DOMContentLoaded', () => {
// Initialize dark mode manager first
window.darkModeManager = new DarkModeManager();
window.connectionManager = new ModernConnectionManager();
// Update timestamp immediately and every second
+71 -3
View File
@@ -66,9 +66,11 @@
<!-- New Devices -->
<div class="col-md-3">
<div class="card h-100">
<div class="card-header">
<i class="fas fa-plus-circle"></i> New Devices
<small class="text-muted d-block" style="font-size: 0.75em; font-weight: normal; margin-top: 2px;">Last 7 days</small>
<div class="card-header d-flex justify-content-between align-items-center">
<span>
<i class="fas fa-plus-circle"></i> New Devices
</span>
<small class="text-muted" style="font-size: 0.75em; font-weight: normal;">Last 7 days</small>
</div>
<div class="card-body d-flex align-items-center">
<div class="row text-center w-100">
@@ -1306,5 +1308,71 @@ document.addEventListener('DOMContentLoaded', () => {
opacity: 1;
color: #0d6efd !important;
}
/* Dark mode table styling with better contrast */
[data-theme="dark"] .table-striped > tbody > tr:nth-of-type(odd) > td {
background-color: #3a3a3a; /* Slightly lighter than even rows, but not too light */
}
[data-theme="dark"] .table-striped > tbody > tr:nth-of-type(even) > td {
background-color: #2d2d2d; /* Darker gray (card-bg) */
}
[data-theme="dark"] .table-hover > tbody > tr:hover > td {
background-color: #404040 !important; /* Slightly lighter for hover, but maintains good contrast */
}
[data-theme="dark"] .table-dark {
background-color: var(--bg-tertiary);
color: var(--text-color);
}
[data-theme="dark"] .table-dark th {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .table td {
border-color: var(--border-color);
color: var(--text-color);
}
/* Ensure all text in table cells is light in dark mode */
[data-theme="dark"] .table td,
[data-theme="dark"] .table td * {
color: var(--text-color) !important;
}
/* Node names (strong tags) should be light and prominent */
[data-theme="dark"] .table td strong {
color: #ffffff !important;
font-weight: 600;
}
/* Ensure small text is also light */
[data-theme="dark"] .table td small {
color: var(--text-muted) !important;
}
/* Dark mode input group styling */
[data-theme="dark"] .input-group-text {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .input-group .form-control {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-color);
}
[data-theme="dark"] .input-group .form-control:focus {
background-color: var(--bg-tertiary);
border-color: #007bff;
color: var(--text-color);
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
</style>
{% endblock %}
+28 -17
View File
@@ -171,14 +171,14 @@
</div>
<div class="card-body text-center">
<div class="row text-center">
<div class="col-6">
<h4 id="unique-users-total" class="text-primary">0</h4>
<small class="text-muted">Total Users</small>
</div>
<div class="col-6">
<h4 id="unique-senders-24h" class="text-info">0</h4>
<small class="text-muted">24h Active</small>
</div>
<div class="col-6">
<h4 id="unique-users-total" class="text-primary">0</h4>
<small class="text-muted">Total Users</small>
</div>
</div>
</div>
</div>
@@ -191,14 +191,14 @@
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6">
<h4 id="total-messages" class="text-primary">0</h4>
<small class="text-muted">Total Messages</small>
</div>
<div class="col-6">
<h4 id="messages-24h" class="text-info">0</h4>
<small class="text-muted">24h Messages</small>
</div>
<div class="col-6">
<h4 id="total-messages" class="text-primary">0</h4>
<small class="text-muted">Total Messages</small>
</div>
</div>
</div>
</div>
@@ -211,14 +211,14 @@
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6">
<h4 id="total-commands" class="text-success">0</h4>
<small class="text-muted">Total Commands</small>
</div>
<div class="col-6">
<h4 id="commands-24h" class="text-warning">0</h4>
<small class="text-muted">24h Commands</small>
</div>
<div class="col-6">
<h4 id="total-commands" class="text-success">0</h4>
<small class="text-muted">Total Commands</small>
</div>
</div>
</div>
</div>
@@ -738,7 +738,7 @@ window.addEventListener('beforeunload', () => {
/* Status indicators moved to enhanced section below */
.loading {
color: #6c757d;
color: var(--text-muted);
font-style: italic;
}
@@ -748,7 +748,7 @@ window.addEventListener('beforeunload', () => {
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
box-shadow: 0 4px 8px var(--shadow-color);
}
.metric-value {
@@ -758,7 +758,7 @@ window.addEventListener('beforeunload', () => {
.metric-label {
font-size: 0.875rem;
color: #6c757d;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
@@ -767,7 +767,7 @@ window.addEventListener('beforeunload', () => {
/* Health status card styling */
.health-status-card .col-md-3 {
border-right: 1px solid #e9ecef;
border-right: 1px solid var(--border-color);
padding: 0.75rem 0.5rem;
position: relative;
}
@@ -793,7 +793,7 @@ window.addEventListener('beforeunload', () => {
.card.shadow-sm:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1) !important;
box-shadow: 0 8px 25px var(--shadow-color) !important;
}
.bg-gradient-primary {
@@ -841,5 +841,16 @@ window.addEventListener('beforeunload', () => {
70% { box-shadow: 0 0 0 6px rgba(40, 167, 69, 0); }
100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
}
[data-theme="dark"] .status-indicator {
box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.4);
animation: pulse-dark 2s infinite;
}
@keyframes pulse-dark {
0% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.6); }
70% { box-shadow: 0 0 0 6px rgba(40, 167, 69, 0); }
100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
}
</style>
{% endblock %}
+43 -9
View File
@@ -189,7 +189,7 @@
const userMessage = document.createElement('div');
userMessage.className = 'd-flex justify-content-start mb-2';
userMessage.innerHTML = `
<div class="user-message p-2 rounded" style="background-color: #e3f2fd; max-width: 80%;">
<div class="user-message p-2 rounded" style="max-width: 80%;">
<div class="d-flex align-items-center mb-1">
<strong class="text-primary me-2">${data.user || 'Unknown'}</strong>
<small class="text-muted me-2">${data.channel || 'Unknown'}</small>
@@ -204,8 +204,9 @@
botMessage.className = 'd-flex justify-content-end mb-2';
const responseText = data.response || 'No response';
const responseClass = data.success ? 'success' : 'danger';
const botMessageClass = data.success ? 'bot-message-success' : 'bot-message-danger';
botMessage.innerHTML = `
<div class="bot-message p-2 rounded" style="background-color: ${data.success ? '#e8f5e8' : '#ffebee'}; max-width: 80%;">
<div class="bot-message p-2 rounded ${botMessageClass}" style="max-width: 80%;">
<div class="d-flex align-items-center mb-1">
<strong class="text-success me-2">🤖 Bot</strong>
<span class="badge bg-${responseClass} small me-2">${data.success ? 'Success' : 'Failed'}</span>
@@ -340,20 +341,21 @@
height: calc(100vh - 300px); /* Use full viewport height minus header/nav space */
min-height: 400px; /* Ensure minimum usable height */
overflow-y: auto;
background-color: #f8f9fa;
border: 1px solid #dee2e6;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 0.375rem;
padding: 0.5rem;
}
.stream-entry {
background-color: white;
background-color: var(--card-bg);
transition: all 0.3s ease;
border-left: 3px solid #007bff;
color: var(--text-color);
}
.stream-entry:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
box-shadow: 0 2px 8px var(--shadow-color);
transform: translateY(-1px);
}
@@ -377,13 +379,45 @@
}
.user-message {
background-color: #e3f2fd;
border: 1px solid #bbdefb;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
box-shadow: 0 1px 3px var(--shadow-color);
color: var(--text-color);
}
[data-theme="dark"] .user-message {
background-color: #1a237e;
border-color: #283593;
color: #e3f2fd;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.bot-message {
.bot-message-success {
background-color: #e8f5e8;
border: 1px solid #c8e6c9;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
box-shadow: 0 1px 3px var(--shadow-color);
color: var(--text-color);
}
[data-theme="dark"] .bot-message-success {
background-color: #1b5e20;
border-color: #2e7d32;
color: #c8e6c9;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.bot-message-danger {
background-color: #ffebee;
border: 1px solid #ffcdd2;
box-shadow: 0 1px 3px var(--shadow-color);
color: var(--text-color);
}
[data-theme="dark"] .bot-message-danger {
background-color: #4a148c;
border-color: #6a1b9a;
color: #f8bbd0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.response-text {