Skip to content

Commit e713c32

Browse files
challgrenclaude
andcommitted
Enhance aircraft info modals and use data directory for persistence
Aircraft Modal Enhancements: - Increased modal size (280-350px width) with better styling - Added TAR1090 radar links for all aircraft - Enhanced content with aircraft ID, type, category details - Professional styling with custom CSS classes - Pattern-specific icons (πŸ”΄ circles, 🟒 grids, ✈️ regular) Data Directory Changes: - History files now stored in /app/data/ directory - Supports Docker volume mounting for persistence - Updated log commands to use data directory - Created directory automatically with proper permissions Benefits: - Better user experience with comprehensive aircraft information - Direct radar access from popups - Persistent data storage across container restarts - Clean separation of application and data πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c1dfce5 commit e713c32

2 files changed

Lines changed: 125 additions & 22 deletions

File tree

β€ŽDockerfileβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ RUN python3 -m pip install --no-cache-dir --break-system-packages -r requirement
4848
# Copy rootfs overlay (includes static files)
4949
COPY rootfs/ /
5050

51+
# Create data directory for persistent storage
52+
RUN mkdir -p /app/data && \
53+
chmod 755 /app/data
54+
5155
# Ensure scripts are executable
5256
RUN chmod +x /etc/s6-overlay/s6-rc.d/aircraft-circle/run && \
5357
chmod +x /scripts/healthcheck.py

β€Žapp.pyβ€Ž

Lines changed: 121 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,58 @@
204204
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
205205
z-index: 1000;
206206
}
207+
208+
/* Enhanced popup styling */
209+
.leaflet-popup-content-wrapper {
210+
border-radius: 8px;
211+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
212+
}
213+
214+
.leaflet-popup-content {
215+
margin: 16px 20px;
216+
line-height: 1.6;
217+
font-size: 14px;
218+
min-width: 280px;
219+
max-width: 350px;
220+
}
221+
222+
.aircraft-popup {
223+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
224+
}
225+
226+
.aircraft-popup .aircraft-title {
227+
font-size: 16px;
228+
font-weight: bold;
229+
color: #2c3e50;
230+
margin-bottom: 8px;
231+
border-bottom: 2px solid #3498db;
232+
padding-bottom: 4px;
233+
}
234+
235+
.aircraft-popup .aircraft-details {
236+
display: grid;
237+
gap: 4px;
238+
margin-bottom: 10px;
239+
}
240+
241+
.aircraft-popup .aircraft-link {
242+
display: inline-block;
243+
background: #3498db;
244+
color: white;
245+
padding: 8px 16px;
246+
border-radius: 4px;
247+
text-decoration: none;
248+
font-weight: 500;
249+
text-align: center;
250+
margin-top: 8px;
251+
transition: background-color 0.2s;
252+
}
253+
254+
.aircraft-popup .aircraft-link:hover {
255+
background: #2980b9;
256+
color: white;
257+
text-decoration: none;
258+
}
207259
</style>
208260
</head>
209261
<body>
@@ -517,11 +569,18 @@
517569
}).addTo(aircraftLayer);
518570
519571
marker.bindPopup(`
520-
<strong>${circle.callsign}</strong><br>
521-
Circling: ${circle.turns.toFixed(1)} turns<br>
522-
Radius: ${circle.radius.toFixed(1)} km<br>
523-
${circle.current_alt ? `Altitude: ${circle.current_alt.toLocaleString()} ft<br>` : ''}
524-
${circle.current_speed ? `Speed: ${circle.current_speed} kts` : ''}
572+
<div class="aircraft-popup">
573+
<div class="aircraft-title">πŸ”΄ ${circle.callsign}</div>
574+
<div class="aircraft-details">
575+
<div><strong>Pattern:</strong> Circling (${circle.turns.toFixed(1)} turns)</div>
576+
<div><strong>Radius:</strong> ${circle.radius.toFixed(1)} km</div>
577+
${circle.current_alt ? `<div><strong>Altitude:</strong> ${circle.current_alt.toLocaleString()} ft</div>` : ''}
578+
${circle.current_speed ? `<div><strong>Speed:</strong> ${circle.current_speed} kts</div>` : ''}
579+
<div><strong>Aircraft ID:</strong> ${circle.hex_id}</div>
580+
${circle.type ? `<div><strong>Type:</strong> ${circle.type}</div>` : ''}
581+
</div>
582+
${circle.tar1090_radar_url ? `<a href="${circle.tar1090_radar_url}" target="_blank" class="aircraft-link">πŸ“‘ View on Radar</a>` : ''}
583+
</div>
525584
`);
526585
527586
visibleAircraftCount++;
@@ -577,12 +636,19 @@
577636
}).addTo(aircraftLayer);
578637
579638
marker.bindPopup(`
580-
<strong>${grid.callsign}</strong><br>
581-
Pattern: ${grid.pattern_type}<br>
582-
Legs: ${grid.num_legs}<br>
583-
Coverage: ${grid.coverage_area.toFixed(1)} kmΒ²<br>
584-
${grid.current_alt ? `Altitude: ${grid.current_alt.toLocaleString()} ft<br>` : ''}
585-
${grid.current_speed ? `Speed: ${grid.current_speed} kts` : ''}
639+
<div class="aircraft-popup">
640+
<div class="aircraft-title">🟒 ${grid.callsign}</div>
641+
<div class="aircraft-details">
642+
<div><strong>Pattern:</strong> ${grid.pattern_type}</div>
643+
<div><strong>Grid Legs:</strong> ${grid.num_legs}</div>
644+
<div><strong>Coverage:</strong> ${grid.coverage_area.toFixed(1)} kmΒ²</div>
645+
${grid.current_alt ? `<div><strong>Altitude:</strong> ${grid.current_alt.toLocaleString()} ft</div>` : ''}
646+
${grid.current_speed ? `<div><strong>Speed:</strong> ${grid.current_speed} kts</div>` : ''}
647+
<div><strong>Aircraft ID:</strong> ${grid.hex_id}</div>
648+
${grid.type ? `<div><strong>Type:</strong> ${grid.type}</div>` : ''}
649+
</div>
650+
${grid.tar1090_radar_url ? `<a href="${grid.tar1090_radar_url}" target="_blank" class="aircraft-link">πŸ“‘ View on Radar</a>` : ''}
651+
</div>
586652
`);
587653
588654
visibleAircraftCount++;
@@ -625,10 +691,17 @@
625691
}).addTo(aircraftLayer);
626692
627693
marker.bindPopup(`
628-
<strong>${aircraft.callsign}</strong><br>
629-
Type: ${aircraft.type || 'Unknown'}<br>
630-
${aircraft.current_alt ? `Altitude: ${aircraft.current_alt.toLocaleString()} ft<br>` : ''}
631-
${aircraft.current_speed ? `Speed: ${aircraft.current_speed} kts` : ''}
694+
<div class="aircraft-popup">
695+
<div class="aircraft-title">✈️ ${aircraft.callsign}</div>
696+
<div class="aircraft-details">
697+
<div><strong>Type:</strong> ${aircraft.type || 'Unknown'}</div>
698+
${aircraft.current_alt ? `<div><strong>Altitude:</strong> ${aircraft.current_alt.toLocaleString()} ft</div>` : ''}
699+
${aircraft.current_speed ? `<div><strong>Speed:</strong> ${aircraft.current_speed} kts</div>` : ''}
700+
<div><strong>Aircraft ID:</strong> ${aircraft.hex_id}</div>
701+
${aircraft.category ? `<div><strong>Category:</strong> ${aircraft.category}</div>` : ''}
702+
</div>
703+
${aircraft.tar1090_radar_url ? `<a href="${aircraft.tar1090_radar_url}" target="_blank" class="aircraft-link">πŸ“‘ View on Radar</a>` : ''}
704+
</div>
632705
`);
633706
634707
visibleAircraftCount++;
@@ -1878,8 +1951,11 @@ def __init__(self, server_url: str, update_interval: int = 5):
18781951
self.active_grids: Set[str] = set() # Track aircraft currently in grid patterns
18791952
self.circle_start_times: Dict[str, float] = {} # Track when circling started
18801953
self.grid_start_times: Dict[str, float] = {} # Track when grid pattern started
1881-
self.log_file = Path("circle_detections.csv")
1882-
self.grid_log_file = Path("grid_detections.csv")
1954+
# Use data directory for persistent storage
1955+
self.data_dir = Path("/app/data")
1956+
self.data_dir.mkdir(parents=True, exist_ok=True)
1957+
self.log_file = self.data_dir / "circle_detections.csv"
1958+
self.grid_log_file = self.data_dir / "grid_detections.csv"
18831959
self.tar1090_base_url = "https://radar.hallgren.net/map"
18841960

18851961
# Display settings
@@ -2438,13 +2514,17 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24382514

24392515
# Add circling aircraft
24402516
for aircraft, detection in self.get_circling_aircraft():
2517+
# Generate TAR1090 radar URL for circling aircraft
2518+
tar1090_radar_url = f"{self.tar1090_base_url}/?icao={aircraft.hex_id}&zoom=12"
2519+
24412520
circle_data = {
24422521
'hex_id': aircraft.hex_id,
24432522
'callsign': aircraft.callsign,
24442523
'center_lat': detection.center_lat,
24452524
'center_lon': detection.center_lon,
24462525
'radius': detection.radius,
24472526
'turns': detection.turns,
2527+
'tar1090_radar_url': tar1090_radar_url,
24482528
'path': [
24492529
{'lat': p.lat, 'lon': p.lon, 'alt': p.altitude, 'time': p.timestamp}
24502530
for p in aircraft.path
@@ -2458,6 +2538,9 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24582538

24592539
# Add grid aircraft
24602540
for aircraft, detection in self.get_grid_aircraft():
2541+
# Generate TAR1090 radar URL for grid aircraft
2542+
tar1090_radar_url = f"{self.tar1090_base_url}/?icao={aircraft.hex_id}&zoom=12"
2543+
24612544
grid_data = {
24622545
'hex_id': aircraft.hex_id,
24632546
'callsign': aircraft.callsign,
@@ -2467,6 +2550,7 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24672550
'grid_bearing': detection.grid_bearing,
24682551
'line_spacing': detection.line_spacing,
24692552
'num_legs': detection.num_legs,
2553+
'tar1090_radar_url': tar1090_radar_url,
24702554
'coverage_area': detection.coverage_area,
24712555
'path': [
24722556
{'lat': p.lat, 'lon': p.lon, 'alt': p.altitude, 'time': p.timestamp}
@@ -2494,6 +2578,9 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
24942578
# Limit track points for performance
24952579
track_points = aircraft.path[-max_track_points:] if len(aircraft.path) > max_track_points else aircraft.path
24962580

2581+
# Generate TAR1090 radar URL for live aircraft
2582+
tar1090_radar_url = f"{self.tar1090_base_url}/?icao={aircraft.hex_id}&zoom=12"
2583+
24972584
aircraft_data = {
24982585
'hex_id': aircraft.hex_id,
24992586
'callsign': aircraft.callsign,
@@ -2505,6 +2592,7 @@ def get_pattern_data_json(self, include_all_aircraft=True, max_track_points=50):
25052592
'current_speed': aircraft.path[-1].speed if aircraft.path else None,
25062593
'type': aircraft.type,
25072594
'category': aircraft.category,
2595+
'tar1090_radar_url': tar1090_radar_url,
25082596
'in_pattern': False
25092597
}
25102598
data['all_aircraft'].append(aircraft_data)
@@ -2776,7 +2864,8 @@ def main():
27762864

27772865
# Handle log-related commands first
27782866
if args.show_log:
2779-
log_file = Path("circle_detections.csv")
2867+
data_dir = Path("/app/data")
2868+
log_file = data_dir / "circle_detections.csv"
27802869
if not log_file.exists():
27812870
print("πŸ“‹ No log file found. Start monitoring to create one.")
27822871
sys.exit(0)
@@ -2808,12 +2897,22 @@ def main():
28082897
sys.exit(0)
28092898

28102899
if args.clear_log:
2811-
log_file = Path("circle_detections.csv")
2900+
data_dir = Path("/app/data")
2901+
log_file = data_dir / "circle_detections.csv"
2902+
grid_log_file = data_dir / "grid_detections.csv"
2903+
2904+
cleared = False
28122905
if log_file.exists():
28132906
log_file.unlink()
2814-
print("βœ… Log file cleared.")
2815-
else:
2816-
print("πŸ“‹ No log file to clear.")
2907+
print("βœ… Circle log file cleared.")
2908+
cleared = True
2909+
if grid_log_file.exists():
2910+
grid_log_file.unlink()
2911+
print("βœ… Grid log file cleared.")
2912+
cleared = True
2913+
2914+
if not cleared:
2915+
print("πŸ“‹ No log files to clear.")
28172916
sys.exit(0)
28182917

28192918
# Create monitor with custom settings

0 commit comments

Comments
Β (0)