diff --git a/assets/static/js/app.js b/assets/static/js/app.js
index 4ba39d3..dc271f4 100644
--- a/assets/static/js/app.js
+++ b/assets/static/js/app.js
@@ -469,10 +469,40 @@ class SkyView {
this.radar3d.renderer.render(this.radar3d.scene, this.radar3d.camera);
}
+ updateOpenPopupAges() {
+ // Find any open aircraft popups and update their age displays
+ if (!this.aircraftManager) return;
+
+ this.aircraftManager.aircraftMarkers.forEach((marker, icao) => {
+ if (marker.isPopupOpen()) {
+ const aircraft = this.aircraftManager.aircraftData.get(icao);
+ if (aircraft) {
+ // Refresh the popup content with current age
+ marker.setPopupContent(this.aircraftManager.createPopupContent(aircraft));
+
+ // Re-enhance callsign display for the updated popup
+ const popupElement = marker.getPopup().getElement();
+ if (popupElement) {
+ this.aircraftManager.enhanceCallsignDisplay(popupElement);
+ }
+ }
+ }
+ });
+ }
+
startPeriodicTasks() {
// Update clocks every second
setInterval(() => this.uiManager.updateClocks(), 1000);
+ // Update aircraft ages and refresh displays every second
+ setInterval(() => {
+ // Update aircraft table to show current ages
+ this.uiManager.updateAircraftTable();
+
+ // Update any open aircraft popups with current ages
+ this.updateOpenPopupAges();
+ }, 1000);
+
// Update charts every 10 seconds
setInterval(() => this.updateCharts(), 10000);
diff --git a/assets/static/js/modules/aircraft-manager.js b/assets/static/js/modules/aircraft-manager.js
index d2bbfdd..81de56d 100644
--- a/assets/static/js/modules/aircraft-manager.js
+++ b/assets/static/js/modules/aircraft-manager.js
@@ -491,7 +491,7 @@ export class AircraftManager {
Messages: ${aircraft.TotalMessages || 0}
- Age: ${aircraft.Age ? aircraft.Age.toFixed(1) : '0'}s
+ Age: ${this.calculateAge(aircraft).toFixed(1)}s
@@ -513,6 +513,16 @@ export class AircraftManager {
return minDistance === Infinity ? null : minDistance;
}
+ calculateAge(aircraft) {
+ if (!aircraft.last_update) return 0;
+
+ const lastUpdate = new Date(aircraft.last_update);
+ const now = new Date();
+ const ageMs = now.getTime() - lastUpdate.getTime();
+
+ return Math.max(0, ageMs / 1000); // Return age in seconds, minimum 0
+ }
+
// Enhance callsign display in popup after it's created
async enhanceCallsignDisplay(popupElement) {
if (!this.callsignManager) return;
diff --git a/assets/static/js/modules/ui-manager.js b/assets/static/js/modules/ui-manager.js
index b891096..d72c8e7 100644
--- a/assets/static/js/modules/ui-manager.js
+++ b/assets/static/js/modules/ui-manager.js
@@ -129,7 +129,7 @@ export class UIManager {
${aircraft.Track || '-'}° |
${sources} |
${bestSignal ? bestSignal.toFixed(1) : '-'} |
- ${aircraft.Age ? aircraft.Age.toFixed(0) : '0'}s |
+ ${this.calculateAge(aircraft).toFixed(0)}s |
`;
row.addEventListener('click', () => {
@@ -161,6 +161,16 @@ export class UIManager {
return 'commercial';
}
+ calculateAge(aircraft) {
+ if (!aircraft.last_update) return 0;
+
+ const lastUpdate = new Date(aircraft.last_update);
+ const now = new Date();
+ const ageMs = now.getTime() - lastUpdate.getTime();
+
+ return Math.max(0, ageMs / 1000); // Return age in seconds, minimum 0
+ }
+
getBestSignalFromSources(sources) {
if (!sources) return null;
let bestSignal = -999;
diff --git a/internal/merger/merger.go b/internal/merger/merger.go
index dd049f1..0d907ce 100644
--- a/internal/merger/merger.go
+++ b/internal/merger/merger.go
@@ -202,7 +202,7 @@ func (a *AircraftState) MarshalJSON() ([]byte, error) {
SpeedHistory: a.SpeedHistory,
Distance: a.Distance,
Bearing: a.Bearing,
- Age: a.Age,
+ Age: time.Since(a.LastUpdate).Seconds(),
MLATSources: a.MLATSources,
PositionSource: a.PositionSource,
UpdateRate: a.UpdateRate,
diff --git a/internal/merger/merger_test.go b/internal/merger/merger_test.go
new file mode 100644
index 0000000..bb1c2e0
--- /dev/null
+++ b/internal/merger/merger_test.go
@@ -0,0 +1,92 @@
+package merger
+
+import (
+ "encoding/json"
+ "testing"
+ "time"
+
+ "skyview/internal/modes"
+)
+
+func TestAircraftState_MarshalJSON_AgeCalculation(t *testing.T) {
+ // Create a test aircraft state with a known LastUpdate time
+ pastTime := time.Now().Add(-30 * time.Second) // 30 seconds ago
+
+ state := &AircraftState{
+ Aircraft: &modes.Aircraft{
+ ICAO24: 0x123456,
+ Callsign: "TEST123",
+ },
+ LastUpdate: pastTime,
+ }
+
+ // Marshal to JSON
+ jsonData, err := json.Marshal(state)
+ if err != nil {
+ t.Fatalf("Failed to marshal AircraftState: %v", err)
+ }
+
+ // Parse the JSON to check the age field
+ var result map[string]interface{}
+ if err := json.Unmarshal(jsonData, &result); err != nil {
+ t.Fatalf("Failed to unmarshal JSON: %v", err)
+ }
+
+ // Check that age is not 0 and is approximately 30 seconds
+ age, ok := result["age"].(float64)
+ if !ok {
+ t.Fatal("Age field not found or not a number")
+ }
+
+ if age < 25 || age > 35 {
+ t.Errorf("Expected age to be around 30 seconds, got %f", age)
+ }
+
+ if age == 0 {
+ t.Error("Age should not be 0 - this indicates the bug is still present")
+ }
+
+ t.Logf("Aircraft age calculated correctly: %f seconds", age)
+}
+
+func TestAircraftState_MarshalJSON_ZeroAge(t *testing.T) {
+ // Test with very recent time to ensure age is very small but not exactly 0
+ recentTime := time.Now()
+
+ state := &AircraftState{
+ Aircraft: &modes.Aircraft{
+ ICAO24: 0x123456,
+ Callsign: "TEST123",
+ },
+ LastUpdate: recentTime,
+ }
+
+ // Marshal to JSON
+ jsonData, err := json.Marshal(state)
+ if err != nil {
+ t.Fatalf("Failed to marshal AircraftState: %v", err)
+ }
+
+ // Parse the JSON to check the age field
+ var result map[string]interface{}
+ if err := json.Unmarshal(jsonData, &result); err != nil {
+ t.Fatalf("Failed to unmarshal JSON: %v", err)
+ }
+
+ // Check that age is very small but should not be exactly 0 (unless extremely fast)
+ age, ok := result["age"].(float64)
+ if !ok {
+ t.Fatal("Age field not found or not a number")
+ }
+
+ if age < 0 {
+ t.Errorf("Age should not be negative, got %f", age)
+ }
+
+ // Age should be very small (less than 1 second) but might not be exactly 0
+ if age > 1.0 {
+ t.Errorf("Age should be very small for recent update, got %f", age)
+ }
+
+ t.Logf("Recent aircraft age calculated correctly: %f seconds", age)
+}