diff --git a/app/routes/api.py b/app/routes/api.py index 3356c60..821a9f1 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -1061,6 +1061,32 @@ def get_device_info(): }), 500 +@api_bp.route('/device/stats', methods=['GET']) +def get_device_stats(): + """ + Get device statistics (uptime, radio, packets). + """ + try: + dm = current_app.config.get('DEVICE_MANAGER') + if not dm or not dm.is_connected: + return jsonify({'success': False, 'error': 'Device not connected'}), 503 + + stats = dm.get_device_stats() + bat = dm.get_battery() + db_stats = dm.db.get_stats() if dm.db else {} + + return jsonify({ + 'success': True, + 'stats': stats, + 'battery': bat, + 'db_stats': db_stats, + }), 200 + + except Exception as e: + logger.error(f"Error getting device stats: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + # ============================================================================= # Special Commands # ============================================================================= diff --git a/app/static/js/app.js b/app/static/js/app.js index 41bb464..3b92b12 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -1338,6 +1338,102 @@ async function loadDeviceInfo() { } } +/** + * Load device statistics (Stats tab in Device modal) + */ +async function loadDeviceStats() { + const container = document.getElementById('deviceStatsContent'); + if (!container) return; + + container.innerHTML = '
Loading...
'; + + try { + const response = await fetch('/api/device/stats'); + const data = await response.json(); + + if (!data.success) { + container.innerHTML = `
${escapeHtml(data.error)}
`; + return; + } + + const stats = data.stats || {}; + const bat = data.battery || {}; + let html = ''; + + // Battery + if (bat && typeof bat === 'object' && bat.voltage) { + html += ``; + } else if (bat) { + html += ``; + } + + // Core stats + if (stats.core) { + const c = stats.core; + if (c.uptime !== undefined) { + const d = Math.floor(c.uptime / 86400); + const h = Math.floor((c.uptime % 86400) / 3600); + const m = Math.floor((c.uptime % 3600) / 60); + html += ``; + } + if (c.queue_length !== undefined) + html += ``; + if (c.errors !== undefined) + html += ``; + } + + // Radio stats + if (stats.radio) { + const r = stats.radio; + if (r.tx_air_time !== undefined) + html += ``; + if (r.rx_air_time !== undefined) + html += ``; + } + + // Packet stats + if (stats.packets) { + const p = stats.packets; + if (p.sent !== undefined) + html += ``; + if (p.received !== undefined) + html += ``; + } + + // DB stats (included in same response) + if (data.db_stats) { + const db = data.db_stats; + if (db.contacts !== undefined) + html += ``; + if (db.channel_messages !== undefined) + html += ``; + if (db.direct_messages !== undefined) + html += ``; + if (db.db_size_bytes !== undefined) { + const sizeMB = (db.db_size_bytes / (1024 * 1024)).toFixed(1); + html += ``; + } + } + + html += '
Battery${bat.voltage}V
Battery${bat}
Uptime${d}d ${h}h ${m}m
Queue${c.queue_length}
Errors${c.errors}
TX Air Time${r.tx_air_time.toFixed(1)} min
RX Air Time${r.rx_air_time.toFixed(1)} min
Packets TX${p.sent.toLocaleString()}
Packets RX${p.received.toLocaleString()}
Contacts (DB)${db.contacts}
Channel Msgs${db.channel_messages.toLocaleString()}
Direct Msgs${db.direct_messages.toLocaleString()}
DB Size${sizeMB} MB
'; + + if (html === '
') { + container.innerHTML = '
No statistics available
'; + } else { + container.innerHTML = html; + } + + } catch (error) { + console.error('Error loading device stats:', error); + container.innerHTML = '
Failed to load stats
'; + } +} + +// Load stats when Stats tab is clicked +document.addEventListener('DOMContentLoaded', () => { + document.getElementById('statsTabBtn')?.addEventListener('shown.bs.tab', loadDeviceStats); +}); + /** * Cleanup inactive contacts */ diff --git a/app/templates/base.html b/app/templates/base.html index 199e714..224466c 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -284,13 +284,32 @@