Files
meshcore-bot/modules/web_viewer/templates/base.html
T
2025-10-21 22:07:50 -07:00

334 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}MeshCore Bot Data Viewer{% endblock %}</title>
<!-- Bootstrap 5.1.3 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome for icons -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- Custom styles -->
<style>
.navbar-brand {
font-weight: bold;
color: #007bff !important;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 8px;
}
.status-connected { background-color: #28a745; }
.status-disconnected { background-color: #dc3545; }
.status-warning { background-color: #ffc107; }
.log-entry {
font-family: 'Courier New', monospace;
font-size: 0.9em;
margin-bottom: 5px;
padding: 8px;
border-left: 3px solid #007bff;
background-color: #f8f9fa;
border-radius: 0 5px 5px 0;
}
.command-entry {
border-left-color: #28a745;
}
.packet-entry {
border-left-color: #ffc107;
}
.connection-info {
background-color: #e9ecef;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border: none;
}
.card-header {
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
font-weight: 600;
}
.btn-group .btn {
margin-right: 5px;
}
.table-responsive {
border-radius: 8px;
overflow: hidden;
}
.badge {
font-size: 0.8em;
}
.footer {
background-color: #f8f9fa;
padding: 20px 0;
margin-top: 50px;
border-top: 1px solid #dee2e6;
}
.loading {
text-align: center;
padding: 20px;
color: #6c757d;
}
.error {
color: #dc3545;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.success {
color: #155724;
background-color: #d4edda;
border: 1px solid #c3e6cb;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<i class="fas fa-satellite-dish"></i> MeshCore Bot
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/">
<i class="fas fa-tachometer-alt"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/realtime">
<i class="fas fa-broadcast-tower"></i> Real-time
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/contacts">
<i class="fas fa-users"></i> Contacts
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/cache">
<i class="fas fa-database"></i> Cache
</a>
</li>
</ul>
<!-- Connection Status -->
<div class="navbar-nav">
<div class="nav-item">
<span class="navbar-text">
<span class="status-indicator" id="connection-status"></span>
<span id="connection-text">Disconnected</span>
</span>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="container-fluid mt-4">
{% block content %}{% endblock %}
</div>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-md-6">
<p class="text-muted mb-0">
<i class="fas fa-satellite-dish"></i> MeshCore Bot Data Viewer v2.0
</p>
</div>
<div class="col-md-6 text-end">
<p class="text-muted mb-0">
Last updated: <span id="last-update">Loading...</span>
</p>
</div>
</div>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Socket.IO Client -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js"></script>
<!-- Moment.js for date formatting -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<!-- Base JavaScript -->
<script>
// Global connection management
class ModernConnectionManager {
constructor() {
this.socket = null;
this.connected = false;
this.lastActivity = null;
this.pingInterval = null;
this.initializeSocket();
this.startPingInterval();
}
initializeSocket() {
this.socket = io({
transports: ['websocket', 'polling'],
timeout: 5000,
forceNew: true
});
this.setupSocketEvents();
}
setupSocketEvents() {
this.socket.on('connect', () => {
console.log('Connected to server');
this.connected = true;
this.updateConnectionStatus('Connected', 'connected');
this.lastActivity = new Date();
this.updateLastActivity();
});
this.socket.on('disconnect', (reason) => {
console.log('Disconnected from server:', reason);
this.connected = false;
this.updateConnectionStatus('Disconnected', 'disconnected');
});
this.socket.on('status', (data) => {
console.log('Server status:', data.message);
this.showNotification(data.message, 'info');
});
this.socket.on('error', (data) => {
console.error('Server error:', data.message);
this.showNotification(`Error: ${data.message}`, 'danger');
});
// Modern ping/pong pattern
this.socket.on('pong', () => {
console.log('Pong received from server');
this.lastActivity = new Date();
this.updateLastActivity();
});
}
startPingInterval() {
this.pingInterval = setInterval(() => {
if (this.socket && this.socket.connected) {
this.socket.emit('ping');
}
}, 20000);
}
stopPingInterval() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
}
updateConnectionStatus(text, status) {
const statusElement = document.getElementById('connection-text');
const indicatorElement = document.getElementById('connection-status');
if (statusElement) {
statusElement.textContent = text;
}
if (indicatorElement) {
indicatorElement.className = `status-indicator status-${status}`;
}
}
updateLastActivity() {
if (this.lastActivity) {
const lastUpdateElement = document.getElementById('last-update');
if (lastUpdateElement) {
lastUpdateElement.textContent = moment(this.lastActivity).format('YYYY-MM-DD HH:mm:ss');
}
}
}
showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; max-width: 300px;';
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Auto-remove after 3 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}
}
// Update timestamp function
function updateTimestamp() {
const now = new Date();
const timestamp = now.toISOString().replace('T', ' ').substring(0, 19);
const lastUpdateElement = document.getElementById('last-update');
if (lastUpdateElement) {
lastUpdateElement.textContent = timestamp;
}
}
// Initialize connection manager when page loads
document.addEventListener('DOMContentLoaded', () => {
window.connectionManager = new ModernConnectionManager();
// Update timestamp immediately and every second
updateTimestamp();
setInterval(updateTimestamp, 1000);
});
</script>
{% block extra_js %}{% endblock %}
</body>
</html>