Add debug page for trip objects

Add /trip/<start>/debug endpoint that displays the complete trip object
data in pretty-printed JSON format with syntax highlighting. Includes
both raw data and computed properties for debugging purposes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2025-07-16 11:25:17 +02:00
parent f396d8a62f
commit 0e2c95117c
2 changed files with 186 additions and 0 deletions

128
templates/trip_debug.html Normal file
View file

@ -0,0 +1,128 @@
{% extends "base.html" %}
{% block title %}Debug: {{ trip.title }} ({{ trip.start }}) - Edward Betts{% endblock %}
{% block style %}
<style>
.json-display {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.875rem;
line-height: 1.4;
white-space: pre-wrap;
overflow-x: auto;
max-height: 80vh;
overflow-y: auto;
}
.debug-header {
background-color: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.debug-header h1 {
color: #856404;
margin-bottom: 0.5rem;
}
.debug-header p {
color: #856404;
margin-bottom: 0.5rem;
}
.debug-header .btn {
margin-right: 0.5rem;
}
/* Basic JSON syntax highlighting using CSS */
.json-display .json-key {
color: #d73a49;
font-weight: bold;
}
.json-display .json-string {
color: #032f62;
}
.json-display .json-number {
color: #005cc5;
}
.json-display .json-boolean {
color: #e36209;
font-weight: bold;
}
.json-display .json-null {
color: #6f42c1;
font-weight: bold;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="debug-header">
<h1>🐛 Trip Debug Information</h1>
<p>Raw trip object data for: <strong>{{ trip.title }}</strong></p>
<a href="{{ url_for('trip_page', start=start) }}" class="btn btn-primary">← Back to Trip Page</a>
<button onclick="copyToClipboard()" class="btn btn-secondary">📋 Copy JSON</button>
</div>
<div class="row">
<div class="col-12">
<h3>Trip Object (JSON)</h3>
<div class="json-display" id="jsonDisplay">{{ trip_json }}</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function copyToClipboard() {
const jsonText = document.getElementById('jsonDisplay').textContent;
navigator.clipboard.writeText(jsonText).then(function() {
// Show a temporary notification
const btn = event.target;
const originalText = btn.textContent;
btn.textContent = '✅ Copied!';
btn.classList.remove('btn-secondary');
btn.classList.add('btn-success');
setTimeout(function() {
btn.textContent = originalText;
btn.classList.remove('btn-success');
btn.classList.add('btn-secondary');
}, 2000);
}).catch(function(err) {
console.error('Failed to copy: ', err);
alert('Failed to copy to clipboard');
});
}
// Simple JSON syntax highlighting
function highlightJSON() {
const display = document.getElementById('jsonDisplay');
let content = display.textContent;
// Highlight different JSON elements
content = content.replace(/"([^"]+)":/g, '<span class="json-key">"$1":</span>');
content = content.replace(/"([^"]*)"(?=\s*[,\]\}])/g, '<span class="json-string">"$1"</span>');
content = content.replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>');
content = content.replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>');
content = content.replace(/\bnull\b/g, '<span class="json-null">null</span>');
display.innerHTML = content;
}
// Apply highlighting when page loads
document.addEventListener('DOMContentLoaded', highlightJSON);
</script>
{% endblock %}

View file

@ -4,8 +4,10 @@
import decimal import decimal
import inspect import inspect
import json
import operator import operator
import os.path import os.path
import pprint
import sys import sys
import time import time
import traceback import traceback
@ -586,6 +588,62 @@ def trip_page(start: str) -> str:
) )
@app.route("/trip/<start>/debug")
def trip_debug_page(start: str) -> str:
"""Trip debug page showing raw trip object data."""
route_distances = agenda.travel.load_route_distances(app.config["DATA_DIR"])
trip_list = get_trip_list(route_distances)
prev_trip, trip, next_trip = get_prev_current_and_next_trip(start, trip_list)
if not trip:
flask.abort(404)
# Add Schengen compliance information
trip = agenda.trip_schengen.add_schengen_compliance_to_trip(trip)
# Convert trip object to dictionary for display
trip_dict = {
"start": trip.start.isoformat(),
"name": trip.name,
"private": trip.private,
"travel": trip.travel,
"accommodation": trip.accommodation,
"conferences": trip.conferences,
"events": trip.events,
"flight_bookings": trip.flight_bookings,
"computed_properties": {
"title": trip.title,
"end": trip.end.isoformat() if trip.end else None,
"countries": [{"name": c.name, "alpha_2": c.alpha_2, "flag": c.flag} for c in trip.countries],
"locations": [{"location": loc, "country": {"name": country.name, "alpha_2": country.alpha_2}} for loc, country in trip.locations()],
"total_distance": trip.total_distance(),
"total_co2_kg": trip.total_co2_kg(),
"distances_by_transport_type": trip.distances_by_transport_type(),
"co2_by_transport_type": trip.co2_by_transport_type(),
},
"schengen_compliance": {
"total_days_used": trip.schengen_compliance.total_days_used,
"days_remaining": trip.schengen_compliance.days_remaining,
"is_compliant": trip.schengen_compliance.is_compliant,
"current_180_day_period": [
trip.schengen_compliance.current_180_day_period[0].isoformat(),
trip.schengen_compliance.current_180_day_period[1].isoformat()
],
"days_over_limit": trip.schengen_compliance.days_over_limit,
} if trip.schengen_compliance else None,
}
# Convert to JSON for pretty printing
trip_json = json.dumps(trip_dict, indent=2, default=str)
return flask.render_template(
"trip_debug.html",
trip=trip,
trip_json=trip_json,
start=start,
)
@app.route("/holidays") @app.route("/holidays")
def holiday_list() -> str: def holiday_list() -> str:
"""List of holidays.""" """List of holidays."""