From 95d13c56833b7fb42be054ef1c7e37a75e322fc7 Mon Sep 17 00:00:00 2001 From: Mike Rosseel Date: Mon, 17 Mar 2025 22:19:28 +0100 Subject: [PATCH 1/6] Merged main with the location changes --- shell.nix | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 shell.nix diff --git a/shell.nix b/shell.nix deleted file mode 100644 index d54e32421..000000000 --- a/shell.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ pkgs ? import {} }: - -pkgs.mkShell { - packages = with pkgs; [ - (python39.withPackages (ps: [ ps.pip ])) - pre-commit - ]; -} From c872394fea9ff1b9aec03fa386954cb757aff557 Mon Sep 17 00:00:00 2001 From: Mike Rosseel Date: Wed, 19 Mar 2025 18:35:18 +0100 Subject: [PATCH 2/6] no ubx yet --- python/PiFinder/main.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/PiFinder/main.py b/python/PiFinder/main.py index 079aa480a..93bfd3735 100644 --- a/python/PiFinder/main.py +++ b/python/PiFinder/main.py @@ -889,10 +889,7 @@ def rotate_logs() -> Path: imu = importlib.import_module("PiFinder.imu_pi") cfg = config.Config() gps_type = cfg.get_option("gps_type") - if gps_type == "ublox": - gps_monitor = importlib.import_module("PiFinder.gps_ubx") - else: - gps_monitor = importlib.import_module("PiFinder.gps_gpsd") + gps_monitor = importlib.import_module("PiFinder.gps_gpsd") if args.display is not None: display_hardware = args.display.lower() From 0ec4de8b165aa8d601baa9b66ce7ae0a83c0ad2d Mon Sep 17 00:00:00 2001 From: Mike Rosseel Date: Tue, 25 Mar 2025 08:13:25 +0100 Subject: [PATCH 3/6] Revert "no ubx yet" This reverts commit c872394fea9ff1b9aec03fa386954cb757aff557. --- python/PiFinder/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/PiFinder/main.py b/python/PiFinder/main.py index bd7dc548f..2911a6bdf 100644 --- a/python/PiFinder/main.py +++ b/python/PiFinder/main.py @@ -893,7 +893,10 @@ def rotate_logs() -> Path: imu = importlib.import_module("PiFinder.imu_pi") cfg = config.Config() gps_type = cfg.get_option("gps_type") - gps_monitor = importlib.import_module("PiFinder.gps_gpsd") + if gps_type == "ublox": + gps_monitor = importlib.import_module("PiFinder.gps_ubx") + else: + gps_monitor = importlib.import_module("PiFinder.gps_gpsd") if args.display is not None: display_hardware = args.display.lower() From d266672d0a5aa7049b20fd221e957bdf5f944a13 Mon Sep 17 00:00:00 2001 From: Mike Rosseel Date: Mon, 7 Apr 2025 21:24:38 +0200 Subject: [PATCH 4/6] remove subcomponent logging --- python/views/logs.tpl | 118 +++++------------------------------------- 1 file changed, 13 insertions(+), 105 deletions(-) diff --git a/python/views/logs.tpl b/python/views/logs.tpl index c8c0cf957..f842ea6c0 100644 --- a/python/views/logs.tpl +++ b/python/views/logs.tpl @@ -51,12 +51,10 @@ margin-right: 0; white-space: nowrap; } - .controls select { - height: 36px; - margin: 0; + .log-level-text { + color: #d4d4d4; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; padding: 0 10px; - width: auto; - min-width: fit-content; } .log-stats { color: #888; @@ -122,21 +120,7 @@ - - - + Global Level: INFO
Total lines: 0 @@ -266,9 +250,6 @@ document.addEventListener('DOMContentLoaded', () => { subtree: true, characterData: true }); - - // Load component levels - updateComponentLevels(); }); // Cleanup on page unload @@ -299,94 +280,21 @@ document.getElementById('copyButton').addEventListener('click', function() { }); }); -// Log level management -function updateComponentLevels() { +// Add this function to update the global level text +function updateGlobalLevelText() { fetch('/logs/components') .then(response => response.json()) .then(data => { - const componentSelect = document.getElementById('componentSelect'); - componentSelect.innerHTML = ''; - - // Sort components alphabetically - const sortedComponents = Object.entries(data.components).sort(([a], [b]) => a.localeCompare(b)); - - sortedComponents.forEach(([component, levels]) => { - const option = document.createElement('option'); - option.value = component; - option.textContent = component; - componentSelect.appendChild(option); - }); + const globalLevel = data.global_level || 'INFO'; + document.getElementById('globalLevelText').textContent = globalLevel; }) - .catch(error => console.error('Error fetching component levels:', error)); + .catch(error => console.error('Error fetching global level:', error)); } -// Handle global level change -document.getElementById('globalLevel').addEventListener('change', function(e) { - const newLevel = e.target.value; - fetch('/logs/level', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `level=${encodeURIComponent(newLevel)}` - }) - .then(response => response.json()) - .then(result => { - if (result.status === 'success') { - console.log(`Changed global log level to ${newLevel}`); - } else { - console.error('Failed to update global log level:', result.message); - } - }) - .catch(error => console.error('Error updating global log level:', error)); -}); - -// Handle component selection -document.getElementById('componentSelect').addEventListener('change', function(e) { - const component = e.target.value; - if (!component) { - document.getElementById('componentLevel').style.display = 'none'; - return; - } - - // Show level select and set current level - const levelSelect = document.getElementById('componentLevel'); - levelSelect.style.display = 'block'; - - // Get current level for selected component - fetch('/logs/components') - .then(response => response.json()) - .then(data => { - const currentLevel = data.components[component].current_level; - levelSelect.value = currentLevel; - }); -}); - -// Handle component level change -document.getElementById('componentLevel').addEventListener('change', function(e) { - const component = document.getElementById('componentSelect').value; - const newLevel = e.target.value; - - fetch('/logs/component_level', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `component=${encodeURIComponent(component)}&level=${encodeURIComponent(newLevel)}` - }) - .then(response => response.json()) - .then(result => { - if (result.status === 'success') { - console.log(`Changed ${component} log level to ${newLevel}`); - } else { - console.error('Failed to update log level:', result.message); - } - }) - .catch(error => console.error('Error updating log level:', error)); -}); - -// Initial load of components -updateComponentLevels(); +// Call updateGlobalLevelText periodically +setInterval(updateGlobalLevelText, 5000); +// Initial call +updateGlobalLevelText(); // Set up button event listeners document.getElementById('pauseButton').addEventListener('click', function() { From 7a8377d441ebcfb301782fd15a857daeca1540d9 Mon Sep 17 00:00:00 2001 From: Mike Rosseel Date: Tue, 8 Apr 2025 09:33:29 +0200 Subject: [PATCH 5/6] lessen logs, show progress, fix download zip --- python/PiFinder/gps_ubx.py | 2 +- python/PiFinder/server.py | 2 +- python/views/logs.tpl | 62 +++++++++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/python/PiFinder/gps_ubx.py b/python/PiFinder/gps_ubx.py index d25e2683f..643d8e232 100644 --- a/python/PiFinder/gps_ubx.py +++ b/python/PiFinder/gps_ubx.py @@ -90,7 +90,7 @@ async def process_messages(parser, gps_queue, console_queue, error_info): ) ) else: - logger.warning(f"TIMEGPS message does not qualify: {msg}") + logger.debug(f"TIMEGPS message does not qualify: {msg}") elif msg_class == "NAV-PVT": if all(k in msg for k in ["lat", "lon", "altHAE", "hAcc", "vAcc"]): diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index 3eef87d9d..99be614c2 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -852,7 +852,7 @@ def download_logs(): # Add all log files log_dir = "/home/pifinder/PiFinder_data" for filename in os.listdir(log_dir): - if filename.startswith("pifinder") and filename.endswith(".log"): + if filename.startswith("pifinder.log"): file_path = os.path.join(log_dir, filename) zipf.write(file_path, filename) diff --git a/python/views/logs.tpl b/python/views/logs.tpl index f842ea6c0..9964ba8cc 100644 --- a/python/views/logs.tpl +++ b/python/views/logs.tpl @@ -56,6 +56,24 @@ font-family: 'Consolas', 'Monaco', 'Courier New', monospace; padding: 0 10px; } + .loading-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(255,255,255,.3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; + margin-right: 8px; + vertical-align: middle; + } + @keyframes spin { + to { transform: rotate(360deg); } + } + .btn:disabled { + opacity: 0.7; + cursor: not-allowed; + } .log-stats { color: #888; font-size: 0.9em; @@ -257,10 +275,52 @@ window.addEventListener('beforeunload', () => { clearInterval(updateInterval); }); +// Add download functionality +document.getElementById('downloadButton').addEventListener('click', function() { + const button = this; + const originalContent = button.innerHTML; + + // Disable button and show loading state + button.disabled = true; + button.innerHTML = 'Preparing download...'; + + // Start the download + fetch('/logs/download') + .then(response => { + if (!response.ok) throw new Error('Download failed'); + return response.blob(); + }) + .then(blob => { + // Create a download link + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `logs_${new Date().toISOString().slice(0,19).replace(/[:]/g, '-')}.zip`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + // Reset button state + button.innerHTML = originalContent; + button.disabled = false; + }) + .catch(error => { + console.error('Download error:', error); + button.innerHTML = 'errorDownload Failed'; + setTimeout(() => { + button.innerHTML = originalContent; + button.disabled = false; + }, 2000); + }); +}); + // Add copy to clipboard functionality document.getElementById('copyButton').addEventListener('click', function() { const logContent = document.getElementById('logContent'); - const text = logContent.innerText; + const text = Array.from(logContent.children) + .map(line => line.textContent) + .join('\n'); navigator.clipboard.writeText(text).then(() => { // Visual feedback From dd8d4910a5cc43707a8b49c876b986e84c6269de Mon Sep 17 00:00:00 2001 From: Mike Rosseel Date: Thu, 1 May 2025 05:21:38 +0200 Subject: [PATCH 6/6] logging wip --- python/PiFinder/server.py | 31 +++++++++-- python/logconf_default.json | 2 +- python/views/logs.tpl | 103 +++++++++++++++++++++++++++++++++--- 3 files changed, 123 insertions(+), 13 deletions(-) diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index 99be614c2..e6de70957 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -791,20 +791,43 @@ def stream_logs(): if position == 0 or new_lines: return { 'logs': new_lines, - 'position': new_position + 'position': new_position, + 'file_size': file_size } else: return { 'logs': [], - 'position': position + 'position': position, + 'file_size': file_size } except FileNotFoundError: logger.error(f"Log file not found: {log_file}") - return {'logs': [], 'position': 0} + return {'logs': [], 'position': 0, 'file_size': 0} except Exception as e: logger.error(f"Error streaming logs: {e}") - return {'logs': [], 'position': position} + return {'logs': [], 'position': position, 'file_size': 0} + + @app.route("/logs/file_info") + @auth_required + def get_log_file_info(): + try: + log_file = "/home/pifinder/PiFinder_data/pifinder.log" + file_size = os.path.getsize(log_file) + + # Count total lines + total_lines = 0 + with open(log_file, 'r') as f: + total_lines = sum(1 for _ in f) + + return { + 'filename': os.path.basename(log_file), + 'total_lines': total_lines, + 'file_size': file_size + } + except Exception as e: + logger.error(f"Error getting log file info: {e}") + return {'error': str(e)} @app.route("/logs/current_level") @auth_required diff --git a/python/logconf_default.json b/python/logconf_default.json index 1a1fe1758..42ff7cb49 100644 --- a/python/logconf_default.json +++ b/python/logconf_default.json @@ -82,7 +82,7 @@ ///////////////////////////////////////////////////////////////// ////// GPS Subsystem "GPS": { - "level": "DEBUG" // Set this to DEBUG, to see results parsed from the GPS + "level": "INFO" // Set this to DEBUG, to see results parsed from the GPS }, "GPS.parser": { "level": "INFO" // Set this to DEBUG, to see results parsed from the GPS diff --git a/python/views/logs.tpl b/python/views/logs.tpl index 9964ba8cc..3b811ea78 100644 --- a/python/views/logs.tpl +++ b/python/views/logs.tpl @@ -78,6 +78,16 @@ color: #888; font-size: 0.9em; margin-bottom: 10px; + display: flex; + gap: 15px; + align-items: center; + } + .log-stats .separator { + color: #666; + } + .log-stats span { + display: inline-flex; + align-items: center; } /* Add horizontal scrollbar styles */ .log-container::-webkit-scrollbar, @@ -141,7 +151,11 @@ Global Level: INFO
- Total lines: 0 + File: pifinder.log + | + Position: 0/0 lines + | + Last update: just now
@@ -165,6 +179,55 @@ const BUFFER_SIZE = 100; const LINE_HEIGHT = 20; let updateInterval; let lastLine = ''; +let totalLinesSeen = 0; +let lastUpdateTime = Date.now(); +let currentLine = 0; +let totalFileLines = 0; +let isAtEndOfFile = false; + +function updateStats() { + // Update current line to show the last line number in the view + currentLine = Math.min(totalLinesSeen, totalFileLines); + document.getElementById('currentLine').textContent = currentLine; + document.getElementById('totalLines').textContent = totalFileLines; + + // Only update the time display if we're not paused + if (!isPaused) { + // Calculate time since last update + const now = Date.now(); + const secondsAgo = Math.floor((now - lastUpdateTime) / 1000); + let lastUpdateText; + + if (secondsAgo < 1) { + lastUpdateText = 'just now'; + } else if (secondsAgo < 60) { + lastUpdateText = `${secondsAgo}s ago`; + } else if (secondsAgo < 3600) { + const minutes = Math.floor(secondsAgo / 60); + lastUpdateText = `${minutes}m ago`; + } else { + const hours = Math.floor(secondsAgo / 3600); + lastUpdateText = `${hours}h ago`; + } + + document.getElementById('lastUpdate').textContent = lastUpdateText; + } +} + +function updateFileInfo() { + fetch('/logs/file_info') + .then(response => response.json()) + .then(data => { + if (data.error) { + console.error('Error getting file info:', data.error); + return; + } + document.getElementById('logFilename').textContent = data.filename; + totalFileLines = data.total_lines; + updateStats(); + }) + .catch(error => console.error('Error fetching file info:', error)); +} function fetchLogs() { if (isPaused) return; @@ -172,19 +235,36 @@ function fetchLogs() { fetch(`/logs/stream?position=${currentPosition}`) .then(response => response.json()) .then(data => { - if (!data.logs || data.logs.length === 0) return; + if (!data.logs || data.logs.length === 0) { + // If we're at the end of the file, update the last update time + if (!isAtEndOfFile) { + isAtEndOfFile = true; + lastUpdateTime = Date.now(); + updateStats(); + } + return; + } - currentPosition = data.position; - const logContent = document.getElementById('logContent'); + isAtEndOfFile = false; + let newLinesAdded = false; // Add new logs to buffer, skipping duplicates data.logs.forEach(line => { if (line !== lastLine) { logBuffer.push(line); lastLine = line; + totalLinesSeen++; + newLinesAdded = true; } }); + // Only update position and last update time if new lines were actually added + if (newLinesAdded) { + currentPosition = data.position; + lastUpdateTime = Date.now(); + updateStats(); + } + // Trim buffer if it exceeds size if (logBuffer.length > BUFFER_SIZE) { logBuffer = logBuffer.slice(-BUFFER_SIZE); @@ -231,6 +311,10 @@ function togglePause() { const pauseButton = document.getElementById('pauseButton'); pauseButton.textContent = isPaused ? 'Resume' : 'Pause'; + // Update the last update time when pause state changes + lastUpdateTime = Date.now(); + updateStats(); + if (!isPaused) { // Resume fetching from last position fetchLogs(); @@ -240,6 +324,8 @@ function togglePause() { function restartFromEnd() { currentPosition = 0; logBuffer = []; + totalLinesSeen = 0; // Reset the line counter + isAtEndOfFile = false; isPaused = false; document.getElementById('pauseButton').textContent = 'Pause'; fetchLogs(); @@ -251,6 +337,9 @@ document.addEventListener('DOMContentLoaded', () => { const loadingMessage = document.querySelector('.loading-message'); loadingMessage.style.display = 'flex'; + // Get initial file info + updateFileInfo(); + // Start log fetching fetchLogs(); updateInterval = setInterval(fetchLogs, 1000); @@ -317,10 +406,8 @@ document.getElementById('downloadButton').addEventListener('click', function() { // Add copy to clipboard functionality document.getElementById('copyButton').addEventListener('click', function() { - const logContent = document.getElementById('logContent'); - const text = Array.from(logContent.children) - .map(line => line.textContent) - .join('\n'); + // Use the logBuffer directly instead of DOM elements + const text = logBuffer.join('\n'); navigator.clipboard.writeText(text).then(() => { // Visual feedback