From f61af015adcca5c5bdfde129435511b96762df64 Mon Sep 17 00:00:00 2001 From: Kirubakaran Date: Sun, 14 Sep 2025 19:15:18 +0530 Subject: [PATCH] fix: enhance the UI of status bar --- backend/main.py | 31 +++++- frontend/dashboard.html | 29 +++-- frontend/js/analytics.js | 2 + frontend/js/dashboard.js | 8 +- frontend/js/logs.js | 230 +++++++++++++++++++++++++++++++++++---- frontend/logs.html | 2 +- 6 files changed, 265 insertions(+), 37 deletions(-) diff --git a/backend/main.py b/backend/main.py index db84d47..c7c1a98 100644 --- a/backend/main.py +++ b/backend/main.py @@ -10,6 +10,7 @@ from flask import Flask, jsonify, request, Response from flask_socketio import SocketIO, join_room from flask_cors import CORS from dotenv import load_dotenv +from sqlalchemy import desc # Import your custom core modules and the new models from core.mqtt_client import MqttClient @@ -67,7 +68,11 @@ STATION_TIMEOUT_SECONDS = 10 # --- MQTT Message Handling --- def on_message_handler(station_id, topic, payload): - last_message_timestamps[station_id] = time.time() + + message_type = topic.split('/')[-1] + + if message_type in ['PERIODIC']: + last_message_timestamps[station_id] = time.time() print(f"Main handler received message for station {station_id} on topic {topic}") @@ -261,6 +266,30 @@ def get_stations(): return jsonify(station_list) except Exception as e: return jsonify({"error": f"Database query failed: {e}"}), 500 + + +@app.route('/api/logs/recent/', methods=['GET']) +def get_recent_logs(station_id): + """ + Fetches the 50 most recent logs for a given station from the database. + """ + try: + # Query the MqttLog table, filter by station_id, order by timestamp descending, and take the first 50 + logs = MqttLog.query.filter_by(station_id=station_id).order_by(desc(MqttLog.timestamp)).limit(50).all() + + # We reverse the list so the oldest are first, for correct display order + logs.reverse() + + log_list = [{ + "topic": log.topic, + "payload": log.payload, + "timestamp": log.timestamp.isoformat() + } for log in logs] + + return jsonify(log_list) + except Exception as e: + print(f"Error fetching recent logs: {e}") + return jsonify({"message": "Could not fetch recent logs."}), 500 # --- CSV Export route (UPDATED) --- diff --git a/frontend/dashboard.html b/frontend/dashboard.html index 8b65f75..30a7bcc 100644 --- a/frontend/dashboard.html +++ b/frontend/dashboard.html @@ -14,7 +14,7 @@ - + + + \ No newline at end of file diff --git a/frontend/js/analytics.js b/frontend/js/analytics.js index d0cc335..fe4c26a 100644 --- a/frontend/js/analytics.js +++ b/frontend/js/analytics.js @@ -7,6 +7,7 @@ document.addEventListener('DOMContentLoaded', () => { const stationNameEl = document.getElementById('station-name'); const stationLocationEl = document.getElementById('station-location'); const deviceIdEl = document.getElementById('device-id'); + const productIdEl = document.getElementById('product-id'); const lastUpdateEl = document.getElementById('last-update-status'); const connChip = document.getElementById('connection-status-chip'); const requestLogArea = document.getElementById('request-log-area'); @@ -70,6 +71,7 @@ document.addEventListener('DOMContentLoaded', () => { throw new Error('No station selected. Please go back to the selection page.'); } deviceIdEl.textContent = selectedStation.id; + productIdEl.textContent = selectedStation.product_id; } catch (e) { document.body.innerHTML = `
${e.message}Go Back
`; return; diff --git a/frontend/js/dashboard.js b/frontend/js/dashboard.js index cb94596..f0d2654 100644 --- a/frontend/js/dashboard.js +++ b/frontend/js/dashboard.js @@ -11,6 +11,7 @@ document.addEventListener('DOMContentLoaded', () => { const stationNameEl = document.getElementById('station-name'); const stationLocationEl = document.getElementById('station-location'); const deviceIdEl = document.getElementById('device-id'); + const productIdEl = document.getElementById('product-id'); const lastUpdateEl = document.getElementById('last-update-status'); const stationDiagCodeEl = document.getElementById('station-diag-code'); const backupPowerChip = document.getElementById('backup-power-chip'); @@ -459,7 +460,12 @@ document.addEventListener('DOMContentLoaded', () => { } stationNameEl.textContent = selectedStation.name || 'Unknown Station'; stationLocationEl.textContent = selectedStation.location || 'No location'; - deviceIdEl.textContent = selectedStation.id; + + // This populates the span with id="device-id" with the Station's ID + deviceIdEl.textContent = selectedStation.id; + + // This populates the span with id="product-id" with the Product ID + // productIdEl.textContent = selectedStation.product_id; } catch (e) { document.body.innerHTML = `
${e.message} Go Back
`; return; diff --git a/frontend/js/logs.js b/frontend/js/logs.js index 967e369..2825bfc 100644 --- a/frontend/js/logs.js +++ b/frontend/js/logs.js @@ -1,3 +1,162 @@ +// document.addEventListener('DOMContentLoaded', () => { +// // --- CONFIGURATION --- +// const SOCKET_URL = "http://192.168.1.12:5000"; +// const API_BASE = "http://192.168.1.12:5000/api"; + +// // --- DOM ELEMENT REFERENCES --- +// const stationNameEl = document.getElementById('station-name'); +// const stationLocationEl = document.getElementById('station-location'); +// const deviceIdEl = document.getElementById('device-id'); +// const lastUpdateEl = document.getElementById('last-update-status'); +// const connChip = document.getElementById('connection-status-chip'); +// const requestLogArea = document.getElementById('request-log-area'); +// const eventLogArea = document.getElementById('event-log-area'); +// const clearReqBtn = document.getElementById('clear-req'); +// const clearEvtBtn = document.getElementById('clear-evt'); +// const clearAllBtn = document.getElementById('clear-all'); +// const refreshBtn = document.getElementById('refreshBtn'); +// const downloadBtn = document.getElementById('downloadBtn'); +// const logoutBtn = document.getElementById('logout-btn'); +// const resetBtn = document.getElementById('station-reset-btn'); + +// // --- STATE --- +// let selectedStation = null; +// let socket; +// let statusPollingInterval; + +// // --- HELPER FUNCTIONS --- +// // **MODIFIED** to accept the 'topic' +// const prependLog = (textarea, data, topic) => { +// if (!textarea) return; +// const timestamp = new Date().toLocaleTimeString(); +// const formattedJson = JSON.stringify(data, null, 2); +// // **MODIFIED** to include the topic string +// const newLog = `[${timestamp}] - Topic: ${topic}\n${formattedJson}\n\n---------------------------------\n\n`; +// textarea.value = newLog + textarea.value; +// }; + +// // --- NEW: LOG PERSISTENCE HELPERS --- +// const saveLogs = () => { +// if (!selectedStation) return; +// sessionStorage.setItem(`request_logs_${selectedStation.id}`, requestLogArea.value); +// sessionStorage.setItem(`event_logs_${selectedStation.id}`, eventLogArea.value); +// }; + +// const loadLogs = () => { +// if (!selectedStation) return; +// requestLogArea.value = sessionStorage.getItem(`request_logs_${selectedStation.id}`) || ''; +// eventLogArea.value = sessionStorage.getItem(`event_logs_${selectedStation.id}`) || ''; +// }; + +// const sendCommand = (command, data = null) => { +// if (!selectedStation || !socket || !socket.connected) { +// console.error(`Cannot send command '${command}', not connected.`); +// return; +// } +// const payload = { station_id: selectedStation.id, command: command, data: data }; +// socket.emit('rpc_request', payload); +// }; + +// const checkStationStatus = async () => { +// // ... (This function is unchanged) +// if (!selectedStation) return; +// try { +// const response = await fetch(`${API_BASE}/stations`); +// if (!response.ok) return; +// const stations = await response.json(); +// const thisStation = stations.find(s => s.id === selectedStation.id); +// if (thisStation) { +// stationNameEl.textContent = thisStation.name; +// stationLocationEl.textContent = thisStation.location; +// if (thisStation.status === 'Online') { +// connChip.innerHTML = ` Online`; +// connChip.className = 'cham_chip cham_chip-emerald'; +// } else { +// connChip.innerHTML = ` Offline`; +// connChip.className = 'cham_chip cham_chip-rose'; +// } +// } +// } catch (error) { console.error("Failed to fetch station status:", error); } +// }; + +// // --- INITIALIZATION --- +// try { +// selectedStation = JSON.parse(localStorage.getItem('selected_station')); +// if (!selectedStation || !selectedStation.id) { +// throw new Error('No station selected. Please go back to the selection page.'); +// } +// deviceIdEl.textContent = selectedStation.id; +// } catch (e) { +// document.body.innerHTML = `
${e.message}Go Back
`; +// return; +// } + +// // --- SOCKET.IO CONNECTION --- +// socket = io(SOCKET_URL); +// socket.on('connect', () => { +// console.log("Connected to WebSocket for logs."); +// socket.emit('join_station_room', { station_id: selectedStation.id }); +// }); + +// socket.on('dashboard_update', (message) => { +// const { stationId, topic, data } = message; +// if (stationId !== selectedStation.id) return; + +// lastUpdateEl.textContent = 'Last Recv ' + new Date().toLocaleTimeString(); + +// // **MODIFIED** to pass the 'topic' to prependLog +// if (topic.endsWith('EVENTS')) { +// prependLog(eventLogArea, data, topic); +// } else if (topic.endsWith('REQUEST')) { +// prependLog(requestLogArea, data, topic); +// } + +// // **NEW**: Save the logs after every update +// saveLogs(); +// }); + +// // --- BUTTON EVENT LISTENERS --- +// // **MODIFIED** to clear sessionStorage +// if(clearReqBtn) clearReqBtn.addEventListener('click', () => { +// requestLogArea.value = ''; +// sessionStorage.removeItem(`request_logs_${selectedStation.id}`); +// }); +// if(clearEvtBtn) clearEvtBtn.addEventListener('click', () => { +// eventLogArea.value = ''; +// sessionStorage.removeItem(`event_logs_${selectedStation.id}`); +// }); +// if(clearAllBtn) clearAllBtn.addEventListener('click', () => { +// requestLogArea.value = ''; +// eventLogArea.value = ''; +// sessionStorage.removeItem(`request_logs_${selectedStation.id}`); +// sessionStorage.removeItem(`event_logs_${selectedStation.id}`); +// }); + +// // (The rest of your button listeners are unchanged) +// if(logoutBtn) logoutBtn.addEventListener('click', () => { +// localStorage.clear(); +// window.location.href = 'index.html'; +// }); +// if(refreshBtn) refreshBtn.addEventListener('click', () => location.reload()); +// if(resetBtn) resetBtn.addEventListener('click', () => { +// if (confirm('Are you sure you want to reset the station?')) { +// sendCommand('STATION_RESET'); +// } +// }); + +// // --- STARTUP --- +// checkStationStatus(); +// statusPollingInterval = setInterval(checkStationStatus, 10000); +// loadLogs(); // **NEW**: Load saved logs from this session on startup +// if (typeof lucide !== 'undefined') { +// lucide.createIcons(); +// } +// }); + + + + + document.addEventListener('DOMContentLoaded', () => { // --- CONFIGURATION --- const SOCKET_URL = "http://192.168.1.12:5000"; @@ -7,32 +166,35 @@ document.addEventListener('DOMContentLoaded', () => { const stationNameEl = document.getElementById('station-name'); const stationLocationEl = document.getElementById('station-location'); const deviceIdEl = document.getElementById('device-id'); - const lastUpdateEl = document.getElementById('last-update-status'); - const connChip = document.getElementById('connection-status-chip'); + const productIdEl = document.getElementById('product-id'); const requestLogArea = document.getElementById('request-log-area'); const eventLogArea = document.getElementById('event-log-area'); const clearReqBtn = document.getElementById('clear-req'); const clearEvtBtn = document.getElementById('clear-evt'); const clearAllBtn = document.getElementById('clear-all'); const refreshBtn = document.getElementById('refreshBtn'); - const downloadBtn = document.getElementById('downloadBtn'); - const logoutBtn = document.getElementById('logout-btn'); const resetBtn = document.getElementById('station-reset-btn'); + const logoutBtn = document.getElementById('logout-btn'); // --- STATE --- let selectedStation = null; let socket; - let statusPollingInterval; // --- HELPER FUNCTIONS --- - const prependLog = (textarea, data) => { + + const appendLog = (textarea, data, topic, timestampStr) => { if (!textarea) return; - const timestamp = new Date().toLocaleTimeString(); + const timestamp = new Date(timestampStr).toLocaleTimeString(); const formattedJson = JSON.stringify(data, null, 2); - const newLog = `[${timestamp}]\n${formattedJson}\n\n---------------------------------\n\n`; - textarea.value = newLog + textarea.value; + const newLog = `[${timestamp}] - Topic: ${topic}\n${formattedJson}\n\n---------------------------------\n\n`; + + // Append the new log to the end of the existing text + textarea.value += newLog; + + // Auto-scroll to the bottom to always show the newest log + textarea.scrollTop = textarea.scrollHeight; }; - + const sendCommand = (command, data = null) => { if (!selectedStation || !socket || !socket.connected) { console.error(`Cannot send command '${command}', not connected.`); @@ -63,35 +225,63 @@ document.addEventListener('DOMContentLoaded', () => { } catch (error) { console.error("Failed to fetch station status:", error); } }; + // --- NEW: Fetch recent logs from the database --- + const fetchAndRenderLogs = async () => { + try { + const response = await fetch(`${API_BASE}/logs/recent/${selectedStation.id}`); + if (!response.ok) { + throw new Error('Failed to fetch recent logs'); + } + const logs = await response.json(); + + // Clear text areas before populating + requestLogArea.value = ''; + eventLogArea.value = ''; + + logs.forEach(log => { + if (log.topic.endsWith('EVENTS')) { + appendLog(eventLogArea, log.payload, log.topic, log.timestamp); + } else if (log.topic.endsWith('REQUEST')) { + appendLog(requestLogArea, log.payload, log.topic, log.timestamp); + } + }); + console.log(`Successfully fetched and rendered ${logs.length} recent logs.`); + + } catch (error) { + console.error(error); + } + }; + // --- INITIALIZATION --- try { selectedStation = JSON.parse(localStorage.getItem('selected_station')); if (!selectedStation || !selectedStation.id) { - throw new Error('No station selected. Please go back to the selection page.'); + throw new Error('No station selected.'); } deviceIdEl.textContent = selectedStation.id; + productIdEl.textContent = selectedStation.product_id; } catch (e) { document.body.innerHTML = `
${e.message}Go Back
`; return; } - // --- SOCKET.IO CONNECTION --- + // --- SOCKET.IO FOR LIVE UPDATES --- socket = io(SOCKET_URL); socket.on('connect', () => { - console.log("Connected to WebSocket for logs."); + console.log("Connected to WebSocket for live logs."); socket.emit('join_station_room', { station_id: selectedStation.id }); }); socket.on('dashboard_update', (message) => { const { stationId, topic, data } = message; if (stationId !== selectedStation.id) return; - - lastUpdateEl.textContent = 'Last Recv ' + new Date().toLocaleTimeString(); + // Just prepend live messages as they arrive + const now = new Date().toISOString(); if (topic.endsWith('EVENTS')) { - prependLog(eventLogArea, data); + appendLog(eventLogArea, data, topic, now); } else if (topic.endsWith('REQUEST')) { - prependLog(requestLogArea, data); + appendLog(requestLogArea, data, topic, now); } }); @@ -102,12 +292,13 @@ document.addEventListener('DOMContentLoaded', () => { requestLogArea.value = ''; eventLogArea.value = ''; }); + + // (The rest of your button listeners are unchanged) if(logoutBtn) logoutBtn.addEventListener('click', () => { localStorage.clear(); window.location.href = 'index.html'; }); if(refreshBtn) refreshBtn.addEventListener('click', () => location.reload()); - // if(downloadBtn) downloadBtn.addEventListener('click', () => alert("Download functionality can be added here.")); // Placeholder for download modal if(resetBtn) resetBtn.addEventListener('click', () => { if (confirm('Are you sure you want to reset the station?')) { sendCommand('STATION_RESET'); @@ -115,8 +306,9 @@ document.addEventListener('DOMContentLoaded', () => { }); // --- STARTUP --- + fetchAndRenderLogs(); // Fetch historical logs on page load checkStationStatus(); - statusPollingInterval = setInterval(checkStationStatus, 10000); + if (typeof lucide !== 'undefined') { lucide.createIcons(); } diff --git a/frontend/logs.html b/frontend/logs.html index 62ab592..200c48d 100644 --- a/frontend/logs.html +++ b/frontend/logs.html @@ -219,7 +219,7 @@ - +