Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
1 change: 1 addition & 0 deletions carwings/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
path('api/car/<str:vin>/timers/<int:id>', api_views.CommandTimerRetrieveApiView.as_view(), name='car_timers_api'),
path('api/car/', api_views.cars_api, name='car_api_list'),
path('api/alerts/<str:vin>/', api_views.alerts_api, name='alerts_api'),
path('api/debug-logs/<str:vin>/', api_views.debug_logs_api, name='debug_logs_api'),
path('api/command/<str:vin>/', 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'),
Expand Down
71 changes: 71 additions & 0 deletions ui/templates/ui/car_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ <h1 class="text-2xl sm:text-3xl font-semibold text-gray-800 dark:text-gray-200 a
<button class="tab-btn px-3 py-2 text-gray-600 dark:text-gray-400 hover:text-blue-500 focus:outline-none text-sm sm:text-base" data-tab="carwings">CARWINGS</button>
<button class="tab-btn px-3 py-2 text-gray-600 dark:text-gray-400 hover:text-blue-500 focus:outline-none text-sm sm:text-base" data-tab="basic-info">{% translate "Basic Info" %}</button>
<button class="tab-btn px-3 py-2 text-gray-600 dark:text-gray-400 hover:text-blue-500 focus:outline-none text-sm sm:text-base" data-tab="tcu-config">{% translate "TCU Configuration" %}</button>
<button class="tab-btn px-3 py-2 text-gray-600 dark:text-gray-400 hover:text-blue-500 focus:outline-none text-sm sm:text-base" data-tab="debug-logs">{% translate "Debug Logs" %}</button>
<button class="tab-btn px-3 py-2 hover:text-blue-500 focus:outline-none {% if show_settings %}text-blue-500 border-b-2 border-blue-500{% else %}text-gray-600 dark:text-gray-400{% endif %} text-sm sm:text-base" data-tab="settings">{% translate "Settings" %}</button>
</div>

Expand Down Expand Up @@ -505,6 +506,21 @@ <h2 id="custom-channels-heading">
</div>
</div>

<!-- Debug Logs -->
<div id="debug-logs" class="tab-content">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-200">{% translate "Debug Logs" %}</h2>
<div class="flex items-center gap-2">
<span id="debug-logs-status" class="text-sm text-gray-500 dark:text-gray-400"></span>
<button onclick="refreshDebugLogs()" class="px-3 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 text-sm">{% translate "Refresh" %}</button>
</div>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">{% translate "Server logs filtered by VIN. Useful for debugging connection issues." %}</p>
<div class="overflow-x-auto bg-gray-900 rounded-lg p-4" style="max-height: 500px; overflow-y: auto;">
<pre id="debug-logs-content" class="text-xs text-green-400 font-mono whitespace-pre-wrap">{% translate "Loading..." %}</pre>
</div>
</div>

<div id="settings" class="tab-content {% if show_settings %}active{% endif %}">
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-4">{% translate "Settings" %}</h2>

Expand Down Expand Up @@ -2596,5 +2612,60 @@ <h3 class="text-sm font-semibold text-gray-800 stc-locname"></h3>
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;
}
});
}
});

</script>
{% endblock %}