diff --git a/repeater/web/http_server.py b/repeater/web/http_server.py index 8457db6..817be27 100644 --- a/repeater/web/http_server.py +++ b/repeater/web/http_server.py @@ -66,206 +66,11 @@ class StatsApp: # Create nested API object for routing self.api = APIEndpoints(stats_getter, send_advert_func, self.config, event_loop, daemon_instance, config_path) - # Load template on init - if template_dir: - template_path = os.path.join(template_dir, "dashboard.html") - try: - with open(template_path, "r") as f: - self.dashboard_template = f.read() - logger.info(f"Loaded template from {template_path}") - except FileNotFoundError: - logger.error(f"Template not found: {template_path}") - @cherrypy.expose - def index(self): - """Serve dashboard HTML.""" - return self._serve_template("dashboard.html") - - @cherrypy.expose - def neighbors(self): - """Serve neighbors page.""" - return self._serve_template("neighbors.html") - - @cherrypy.expose - def statistics(self): - """Serve statistics page.""" - return self._serve_template("statistics.html") - - @cherrypy.expose - def configuration(self): - """Serve configuration page.""" - return self._serve_template("configuration.html") - - @cherrypy.expose - def logs(self): - """Serve logs page.""" - return self._serve_template("logs.html") - - @cherrypy.expose - def help(self): - """Serve help documentation.""" - return self._serve_template("help.html") - - @cherrypy.expose - def cad_calibration(self): - """Serve CAD calibration page.""" - return self._serve_template("cad-calibration.html") - - def _serve_template(self, template_name: str): - """Serve HTML template with stats.""" - if not self.template_dir: - return "

Error

Template directory not configured

" - - if not self.dashboard_template: - return "

Error

Template not loaded

" - - try: - - template_path = os.path.join(self.template_dir, template_name) - with open(template_path, "r") as f: - template_content = f.read() - - nav_path = os.path.join(self.template_dir, "nav.html") - nav_content = "" - try: - with open(nav_path, "r") as f: - nav_content = f.read() - except FileNotFoundError: - logger.warning(f"Navigation template not found: {nav_path}") - - stats = self.stats_getter() if self.stats_getter else {} - - if "uptime_seconds" not in stats or not isinstance( - stats.get("uptime_seconds"), (int, float) - ): - stats["uptime_seconds"] = 0 - - # Calculate uptime in hours - uptime_seconds = stats.get("uptime_seconds", 0) - uptime_hours = int(uptime_seconds // 3600) if uptime_seconds else 0 - - # Determine current page for nav highlighting - page_map = { - "dashboard.html": "dashboard", - "neighbors.html": "neighbors", - "statistics.html": "statistics", - "configuration.html": "configuration", - "cad-calibration.html": "cad-calibration", - "logs.html": "logs", - "help.html": "help", - } - current_page = page_map.get(template_name, "") - - # Prepare basic substitutions - html = template_content - html = html.replace("{{ node_name }}", str(self.node_name)) - html = html.replace("{{ last_updated }}", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - html = html.replace("{{ page }}", current_page) - - # Replace navigation placeholder with actual nav content - if "" in html: - nav_substitutions = nav_content - nav_substitutions = nav_substitutions.replace( - "{{ node_name }}", str(self.node_name) - ) - nav_substitutions = nav_substitutions.replace("{{ pub_key }}", str(self.pub_key)) - nav_substitutions = nav_substitutions.replace( - "{{ last_updated }}", datetime.now().strftime("%Y-%m-%d %H:%M:%S") - ) - - # Handle active state for nav items - nav_substitutions = nav_substitutions.replace( - "{{ ' active' if page == 'dashboard' else '' }}", - " active" if current_page == "dashboard" else "", - ) - nav_substitutions = nav_substitutions.replace( - "{{ ' active' if page == 'neighbors' else '' }}", - " active" if current_page == "neighbors" else "", - ) - nav_substitutions = nav_substitutions.replace( - "{{ ' active' if page == 'statistics' else '' }}", - " active" if current_page == "statistics" else "", - ) - nav_substitutions = nav_substitutions.replace( - "{{ ' active' if page == 'configuration' else '' }}", - " active" if current_page == "configuration" else "", - ) - nav_substitutions = nav_substitutions.replace( - "{{ ' active' if page == 'logs' else '' }}", - " active" if current_page == "logs" else "", - ) - nav_substitutions = nav_substitutions.replace( - "{{ ' active' if page == 'help' else '' }}", - " active" if current_page == "help" else "", - ) - - html = html.replace("", nav_substitutions) - - # Build packets table HTML for dashboard - if template_name == "dashboard.html": - recent_packets = stats.get("recent_packets", []) - packets_table = "" - - if recent_packets: - for pkt in recent_packets[-20:]: # Last 20 packets - time_obj = datetime.fromtimestamp(pkt.get("timestamp", 0)) - time_str = time_obj.strftime("%H:%M:%S") - pkt_type = PAYLOAD_TYPES.get( - pkt.get("type", 0), f"0x{pkt.get('type', 0): 02x}" - ) - route_type = pkt.get("route", 0) - route = ROUTE_TYPES.get(route_type, f"UNKNOWN_{route_type}") - status = "OK TX" if pkt.get("transmitted") else "WAIT" - - # Get proper CSS class for route type - route_class = route.lower().replace("_", "-") - snr_val = pkt.get("snr", 0.0) - score_val = pkt.get("score", 0) - delay_val = pkt.get("tx_delay_ms", 0) - - packets_table += ( - "" - f"{time_str}" - f'{pkt_type}' - f'{route}' - f"{pkt.get('length', 0)}" - f"{pkt.get('rssi', 0)}" - f"{snr_val: .1f}" - f'{score_val: .2f}' - f"{delay_val: .0f}" - f"{status}" - "" - ) - else: - packets_table = """ - - - No packets received yet - waiting for traffic... - - - """ - - # Add dashboard-specific substitutions - html = html.replace("{{ rx_count }}", str(stats.get("rx_count", 0))) - html = html.replace("{{ forwarded_count }}", str(stats.get("forwarded_count", 0))) - html = html.replace("{{ dropped_count }}", str(stats.get("dropped_count", 0))) - html = html.replace("{{ uptime_hours }}", str(uptime_hours)) - - # Replace tbody with actual packets - tbody_pattern = r'.*?' - tbody_replacement = f'\n{packets_table}\n' - html = re.sub( - tbody_pattern, - tbody_replacement, - html, - flags=re.DOTALL, - ) - - return html - - except Exception as e: - logger.error(f"Error rendering template {template_name}: {e}", exc_info=True) - return f"

Error

{str(e)}

" + # @cherrypy.expose + # def index(self): + # """Serve dashboard HTML.""" + # return self._serve_template("dashboard.html") class HTTPStatsServer: diff --git a/repeater/web/stats-data-collection.md b/repeater/web/stats-data-collection.md deleted file mode 100644 index b520fd8..0000000 --- a/repeater/web/stats-data-collection.md +++ /dev/null @@ -1,1929 +0,0 @@ -# Stats Data Collection & Charting Examples - -This document provides examples for using the pyMC_Repeater API endpoints to create charts and visualizations for network monitoring. - -## Available API Endpoints - -### Basic Statistics -- `/api/packet_stats` - Get packet statistics for a time period -- `/api/recent_packets` - Get recent packets with all fields -- `/api/filtered_packets` - Get packets with filtering options -- `/api/packet_by_hash` - Get specific packet by hash - -### Time Series Data for Charts -- `/api/packet_type_graph_data` - Get packet type data for graphing -- `/api/metrics_graph_data` - Get metrics data for graphing -- `/api/packet_type_stats` - Get packet type distribution -- `/api/rrd_data` - Get raw RRD time series data - -### Noise Floor Monitoring -- `/api/noise_floor_history` - Get noise floor history (SQLite data) -- `/api/noise_floor_stats` - Get noise floor statistics -- `/api/noise_floor_chart_data` - Get noise floor RRD chart data - -## Noise Floor API Examples - -### Fetch Noise Floor History -```javascript -// Get last 24 hours of noise floor data from SQLite -async function fetchNoiseFloorHistory() { - const response = await fetch('/api/noise_floor_history?hours=24'); - const result = await response.json(); - - if (result.success) { - const history = result.data.history; - console.log(`Found ${history.length} noise floor measurements`); - - // Each record: { timestamp: 1234567890.123, noise_floor_dbm: -95.5 } - history.forEach(record => { - console.log(`${new Date(record.timestamp * 1000).toISOString()}: ${record.noise_floor_dbm} dBm`); - }); - } else { - console.error('Error:', result.error); - } -} -``` - -### Fetch Noise Floor Statistics -```javascript -// Get statistical summary of noise floor data -async function fetchNoiseFloorStats() { - const response = await fetch('/api/noise_floor_stats?hours=24'); - const result = await response.json(); - - if (result.success) { - const stats = result.data.stats; - console.log('Noise Floor Statistics:'); - console.log(`Count: ${stats.count}`); - console.log(`Average: ${stats.average?.toFixed(1)} dBm`); - console.log(`Min: ${stats.min} dBm`); - console.log(`Max: ${stats.max} dBm`); - console.log(`Std Dev: ${stats.std_dev?.toFixed(2)}`); - } -} -``` - -### Fetch Chart-Ready Noise Floor Data -```javascript -// Get RRD-based noise floor data optimized for charts -async function fetchNoiseFloorChartData() { - const response = await fetch('/api/noise_floor_chart_data?hours=24'); - const result = await response.json(); - - if (result.success) { - const chartData = result.data.chart_data; - - // Data points: [[timestamp_ms, noise_floor_dbm], ...] - chartData.data_points.forEach(point => { - const [timestamp_ms, value] = point; - console.log(`${new Date(timestamp_ms).toISOString()}: ${value} dBm`); - }); - - console.log('Statistics:', chartData.statistics); - } -} -``` - -## Noise Floor Chart Examples - -### 1. Noise Floor Time Series (Chart.js) - -```html - - - - - - - - - - - - -``` - -### 2. Noise Floor Distribution Histogram - -```html - - - - - - - - - - - -``` - -### 3. Real-time Noise Floor Monitor - -```html - - - - - - - - -
-

Real-time Noise Floor Monitor

- -
-
-
-- dBm
-
Current
-
-
-
-- dBm
-
1h Average
-
-
-
-- dBm
-
1h Min
-
-
-
-- dBm
-
1h Max
-
-
- - -
- - - - -``` - -## Chart.js Examples - -### 1. Packet Type Distribution (Pie Chart) - -```html - - - - - - - - - - - -``` - -### 2. Packet Types Over Time (Line Chart) - -```html - - - - - - - - - - - - -``` - -### 3. Network Metrics Dashboard (Multiple Charts) - -```html - - - - - - - - -
-
- -
-
- -
-
- -
-
- -
-
- - - - -``` - -## Plotly.js Examples - -### 1. Interactive 3D Packet Type Surface - -```html - - - - - - -
- - - - -``` - -### 2. Heatmap of Packet Activity - -```html - - - - - - -
- - - - -``` - -## D3.js Example - -### Real-time Network Status - -```html - - - - - - - - - - - - -``` - -## Vue.js Framework Integration - -### Vue.js Component Example - -```vue - - - - - -``` - -### Vue.js Packet Type Dashboard - -```vue - - - - - -``` - -### Composable for API Management (Vue 3 Composition API) - -```javascript -// composables/useNetworkAPI.js -import { ref, reactive } from 'vue'; - -export function useNetworkAPI() { - const loading = ref(false); - const error = ref(null); - - const cache = reactive(new Map()); - const CACHE_TTL = 60000; // 1 minute - - async function fetchWithCache(url, forceRefresh = false) { - const cacheKey = url; - const now = Date.now(); - - // Check cache first - if (!forceRefresh && cache.has(cacheKey)) { - const cached = cache.get(cacheKey); - if (now - cached.timestamp < CACHE_TTL) { - return cached.data; - } - } - - loading.value = true; - error.value = null; - - try { - const response = await fetch(url); - const result = await response.json(); - - if (!result.success) { - throw new Error(result.error || 'API request failed'); - } - - // Cache the result - cache.set(cacheKey, { - data: result.data, - timestamp: now - }); - - return result.data; - } catch (err) { - error.value = err.message; - console.error('API Error:', err); - return null; - } finally { - loading.value = false; - } - } - - async function getPacketStats(hours = 24) { - return await fetchWithCache(`/api/packet_stats?hours=${hours}`); - } - - async function getPacketTypeStats(hours = 24) { - return await fetchWithCache(`/api/packet_type_stats?hours=${hours}`); - } - - async function getPacketTypeGraphData(hours = 24, types = 'all') { - const typesParam = types === 'all' ? '' : `&types=${types}`; - return await fetchWithCache(`/api/packet_type_graph_data?hours=${hours}${typesParam}`); - } - - async function getMetricsGraphData(hours = 24, metrics = 'all') { - const metricsParam = metrics === 'all' ? '' : `&metrics=${metrics}`; - return await fetchWithCache(`/api/metrics_graph_data?hours=${hours}${metricsParam}`); - } - - async function getRecentPackets(limit = 100) { - return await fetchWithCache(`/api/recent_packets?limit=${limit}`); - } - - async function getFilteredPackets(filters = {}) { - const params = new URLSearchParams(); - Object.entries(filters).forEach(([key, value]) => { - if (value !== null && value !== undefined) { - params.append(key, value); - } - }); - - return await fetchWithCache(`/api/filtered_packets?${params.toString()}`); - } - - function clearCache() { - cache.clear(); - } - - return { - loading, - error, - getPacketStats, - getPacketTypeStats, - getPacketTypeGraphData, - getMetricsGraphData, - getRecentPackets, - getFilteredPackets, - clearCache - }; -} -``` - -### Simple Vue.js Usage Example - -```vue - - - - - -``` -``` - -## API Usage Tips - -### 1. Error Handling -```javascript -async function fetchWithErrorHandling(url) { - try { - const response = await fetch(url); - const result = await response.json(); - - if (!result.success) { - throw new Error(result.error || 'API request failed'); - } - - return result.data; - } catch (error) { - console.error('API Error:', error); - return null; - } -} -``` - -### 2. Caching Strategy -```javascript -class APICache { - constructor(ttl = 60000) { // 1 minute TTL - this.cache = new Map(); - this.ttl = ttl; - } - - async get(key, fetcher) { - const now = Date.now(); - const cached = this.cache.get(key); - - if (cached && (now - cached.timestamp) < this.ttl) { - return cached.data; - } - - const data = await fetcher(); - this.cache.set(key, { data, timestamp: now }); - return data; - } -} - -const apiCache = new APICache(); - -// Usage -const data = await apiCache.get('metrics-24h', () => - fetch('/api/metrics_graph_data?hours=24').then(r => r.json()) -); -``` - -### 3. Real-time Updates -```javascript -function setupRealTimeUpdates(chartComponent, interval = 30000) { - const updateChart = async () => { - const data = await fetchWithErrorHandling('/api/metrics_graph_data?hours=1'); - if (data) { - chartComponent.updateData(data); - } - }; - - // Initial load - updateChart(); - - // Set up interval - const intervalId = setInterval(updateChart, interval); - - // Return cleanup function - return () => clearInterval(intervalId); -} -``` - -This documentation provides examples for creating various types of charts and visualizations using the pyMC_Repeater API endpoints. The examples cover different chart libraries and use cases, from simple statistics to real-time network monitoring. \ No newline at end of file