From 1d138c7184125559798959a8d1d8733ceca81949 Mon Sep 17 00:00:00 2001 From: Chaoscontrol Date: Sat, 31 Jan 2026 16:11:40 +0000 Subject: [PATCH] feat: Add Debug logs tab --- api/views.py | 37 +++++++++++++++++ carwings/urls.py | 1 + ui/templates/ui/car_detail.html | 71 +++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/api/views.py b/api/views.py index ac7aceb..a71757c 100644 --- a/api/views.py +++ b/api/views.py @@ -375,6 +375,43 @@ def command_api(request, vin): return Response({'error': 'Command type must be an integer'}, status=status.HTTP_400_BAD_REQUEST) +@swagger_auto_schema( + operation_description="Retrieve debug logs for a vehicle", + method='get', + tags=['cars'], + responses={ + 200: 'List of log entries', + 401: 'Not authorized', + 404: 'Car not found', + } +) +@api_view(['GET']) +def debug_logs_api(request, vin): + """Return last N log entries from tcuserver.log filtered by VIN""" + if not request.user.is_authenticated: + return Response(status=status.HTTP_401_UNAUTHORIZED) + + # Verify the car belongs to the user + car = get_object_or_404(Car, vin=vin, owner=request.user) + + log_file_path = 'logs/tcuserver.log' + logs = [] + + try: + import os + if os.path.exists(log_file_path): + with open(log_file_path, 'r', encoding='utf-8', errors='ignore') as f: + # Read all lines and filter by VIN + all_lines = f.readlines() + matching_lines = [line.strip() for line in all_lines if vin in line] + # Return last 100 matching entries + logs = matching_lines[-100:] + except Exception as e: + logs = [f"Error reading log file: {str(e)}"] + + return Response({'logs': logs, 'vin': vin}) + + class CustomTokenObtainPairView(TokenObtainPairView): @swagger_auto_schema(tags=['token'], request_body=JWTTokenObtainPairSerializer()) diff --git a/carwings/urls.py b/carwings/urls.py index e0d8b70..519c1af 100644 --- a/carwings/urls.py +++ b/carwings/urls.py @@ -80,6 +80,7 @@ path('api/car//timers/', api_views.CommandTimerRetrieveApiView.as_view(), name='car_timers_api'), path('api/car/', api_views.cars_api, name='car_api_list'), path('api/alerts//', api_views.alerts_api, name='alerts_api'), + path('api/debug-logs//', api_views.debug_logs_api, name='debug_logs_api'), path('api/command//', api_views.command_api, name='command_api'), path('api/token/obtain/', CustomTokenObtainPairView.as_view(), name='token_refresh'), path('api/token/refresh/', decorated_token_view, name='token_refresh'), diff --git a/ui/templates/ui/car_detail.html b/ui/templates/ui/car_detail.html index ce3a20a..4e64139 100644 --- a/ui/templates/ui/car_detail.html +++ b/ui/templates/ui/car_detail.html @@ -101,6 +101,7 @@

CARWINGS + @@ -505,6 +506,21 @@

+ +
+
+

{% translate "Debug Logs" %}

+
+ + +
+
+

{% translate "Server logs filtered by VIN. Useful for debugging connection issues." %}

+
+
{% translate "Loading..." %}
+
+
+

{% translate "Settings" %}

@@ -2596,5 +2612,60 @@

updateCarData(); updateAlerts(); + // Debug Logs functionality + let debugLogsInterval = null; + const carVin = '{{ car.vin }}'; + + async function refreshDebugLogs() { + const statusEl = document.getElementById('debug-logs-status'); + const contentEl = document.getElementById('debug-logs-content'); + + statusEl.textContent = '{% translate "Loading..." %}'; + + try { + const response = await fetch(`/api/debug-logs/${carVin}/`, { + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin' + }); + + if (response.ok) { + const data = await response.json(); + if (data.logs && data.logs.length > 0) { + contentEl.textContent = data.logs.join('\n'); + } else { + contentEl.textContent = '{% translate "No log entries found for this VIN." %}'; + } + statusEl.textContent = `{% translate "Updated" %}: ${new Date().toLocaleTimeString()}`; + } else { + contentEl.textContent = `{% translate "Error loading logs" %}: ${response.status}`; + statusEl.textContent = ''; + } + } catch (error) { + contentEl.textContent = `{% translate "Error" %}: ${error.message}`; + statusEl.textContent = ''; + } + } + + // Auto-refresh when debug-logs tab is active + document.querySelector('[data-tab="debug-logs"]').addEventListener('click', () => { + refreshDebugLogs(); + if (debugLogsInterval) clearInterval(debugLogsInterval); + debugLogsInterval = setInterval(refreshDebugLogs, 10000); + }); + + // Stop auto-refresh when switching away from debug-logs tab + document.querySelectorAll('.tab-btn').forEach(btn => { + if (btn.getAttribute('data-tab') !== 'debug-logs') { + btn.addEventListener('click', () => { + if (debugLogsInterval) { + clearInterval(debugLogsInterval); + debugLogsInterval = null; + } + }); + } + }); + {% endblock %} \ No newline at end of file