Commit graph

194 commits

Author SHA1 Message Date
77cf28e908
Add connection draining for cluster load balancing
Implements automatic connection draining to balance load across cluster nodes:
- ConnectionMonitor tracks CPU usage and connection counts across nodes
- Triggers draining when node is overloaded (>70% CPU or 2x avg connections)
- LiveViews gracefully disconnect and reconnect to less loaded nodes
- Helps prevent single pod from handling all WebSocket connections

Also fixes Mic-E packet parsing to properly extract altitude data and
remove telemetry suffixes from comments.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 12:34:30 -05:00
ef141d476f
Improve coordinate handling and add configurable position sensitivity
- Fix zero coordinate handling to distinguish between invalid coordinates and valid 0.0 (equator/prime meridian)
- Add to_float_safe() function that preserves nil for proper coordinate validation
- Add comprehensive coordinate validation with descriptive logging in JavaScript
- Make position change threshold configurable via application config (default: 0.001°)
- Add extensive test coverage for edge cases including nil coordinates and equator crossing
- Improve robustness for real-world APRS coordinate edge cases

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 12:06:56 -05:00
77eb5ac05b
Fix info page map flash on packet updates
- Optimize LiveView to only update position data when coordinates actually change (>100m threshold)
- Hide loading spinner after map initialization to prevent flash on updates
- Add position change detection logic with proper nil/Decimal handling
- Add comprehensive test coverage for position change detection
- Maintain smooth map animations and existing functionality

Resolves map flashing and unnecessary reloads when new packets arrive on /info/:call pages.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 12:01:48 -05:00
887c35a1c8
Fix DateTime.to_iso8601 error by handling NaiveDateTime
- Handle both DateTime and NaiveDateTime structs from database
- Convert NaiveDateTime to UTC DateTime before formatting
- Add fallback for string timestamps
- Fixes FunctionClauseError when processing packets
2025-07-29 10:57:36 -05:00
29a811adf2
Enhance info page live updates with comprehensive packet data
Update PostgreSQL notify trigger to send ALL packet fields including:
- Device information (model, vendor, contact, class)
- Weather data fields for weather-enabled stations
- SSID and base_callsign for proper SSID grouping
- data_type for packet type identification

Also added debug logging to verify live updates are working correctly.
This ensures the info page displays complete, real-time information
for all sections including position, device info, nearby stations,
and raw packet data.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 18:00:38 -05:00
521aeea3c5
Remove caching from has_weather_packets function
Remove Cachex caching from the has_weather_packets function to ensure
real-time data display. The function now queries the database directly
for weather packet information.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 15:52:53 -05:00
bc6fbbe3de
Fix map height on info page to fill entire card
- Set map height to 100% instead of fixed 280px
- Removed card-body padding to eliminate gap
- Added h-full class to ensure cards fill available height
- Added min-height of 320px for better UX
- Removed redundant styling from map container

The map now properly fills the entire card height and matches
the position information card height on larger screens.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 15:25:24 -05:00
85c3b3d9e7
Improve info page live updates and fix APRS parser
- Fixed info page not receiving live updates by implementing callsign-specific PubSub topics
- Added automatic time counter for "Last Position" and other timestamps using JavaScript hook
- Optimized map updates to only move marker instead of reloading entire map
- Fixed APRS parser to properly handle [000/000/A=altitude format in comments
- Removed packet caching on info page for real-time updates

The info page now properly subscribes to updates for the specific callsign being viewed,
timestamps automatically count up without page refresh, and the map smoothly animates
marker position changes instead of flickering with full reloads.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 15:14:33 -05:00
dc459b491d
Add debug logging to diagnose info page live update issue
Added debug logging to track packet reception and processing in the
info page LiveView to help diagnose why live updates aren't working.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 14:43:38 -05:00
478e7b1936
Add deployment notification system to update timestamp in LiveViews
When a new deployment occurs in k8s, all connected LiveViews now receive
a notification to update their deployment timestamp display.

Changes:
- Added DeploymentNotifier GenServer that broadcasts deployment events
- LiveViews subscribe to "deployment_events" PubSub topic
- When DEPLOYED_AT env var is set (k8s deployments), a notification is
  sent after app startup
- LiveViews handle the deployment event and update their timestamp

This ensures that when a new version is deployed, users see the
deployment timestamp update in real-time without needing to refresh
their browser.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 13:19:38 -05:00
94c0f1a48c
Fix real-time packet updates not showing on map
The map wasn't receiving real-time packet updates because:
1. LiveView wasn't subscribed to StreamingPacketsPubSub
2. LiveView wasn't handling {:streaming_packet, packet} messages
3. Packets weren't being broadcast to both PubSub systems

Changes:
- Added StreamingPacketsPubSub subscription with viewport bounds filtering
- Added handler for {:streaming_packet, packet} messages
- Update subscription bounds when map viewport changes
- Properly unsubscribe on LiveView termination
- Also broadcast packets to SpatialPubSub (in addition to StreamingPacketsPubSub)

Now the map only receives packets within its current viewport bounds,
reducing unnecessary data transfer and improving performance.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 13:12:06 -05:00
963f8abfc4
Fix 'Last Update' timestamp to show actual time since last packet
The 'Last Update' timestamp now correctly shows when the last real-time
packet was received, not when the map bounds changed or historical data
was loaded.

Changes:
- Removed timestamp update when map bounds change (was incorrectly
  resetting the timer)
- Added periodic UI update timer (every 30 seconds) to refresh the
  'time ago' display so it counts up properly (e.g., "2 minutes ago",
  "5 minutes ago", etc.)
- The timestamp only updates when actual real-time packets are received
  via PacketProcessor

Now the sidebar will show how long it's been since the last packet was
received, updating every 30 seconds to keep the display current.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 12:42:37 -05:00
7e4bbbaff5
Fix deployment timestamp always showing 'less than a minute ago' in k8s
Previously, the deployment timestamp was set to DateTime.utc_now() when
the application started, which meant it would always show as recent in
k8s environments where pods are recreated on each deployment.

This commit:
- Updates Release.init() to check for DEPLOYED_AT environment variable
- Falls back to current time if env var is not set (backwards compatible)
- Adds proper ISO8601 timestamp parsing with error handling
- Updates GitHub Actions deploy workflow to set DEPLOYED_AT during deployment
- Creates k8s patch file for future manual deployments

The deployment timestamp will now persist across pod restarts in k8s,
showing the actual deployment time rather than pod startup time.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 12:06:12 -05:00
d5781f2581
Fix comprehensive crash prevention and add missing RF path station markers
This commit addresses two major areas:

1. Crash Prevention & Error Handling:
   - Added error handling to all database queries to prevent crashes
   - Implemented circuit breaker pattern for APRS-IS connection
   - Added timeouts to all Redis operations (5 second timeout)
   - Fixed unsafe pattern matches that could cause MatchError crashes
   - Added try/rescue blocks around background tasks
   - Improved socket operation error handling with proper cleanup
   - Enhanced packet cleanup worker with comprehensive error handling

2. RF Path Station Markers:
   - Fixed issue where digipeater/iGate icons weren't shown when searching for callsigns
   - Now loads and displays position markers for all stations in the RF path
   - Extracts RF path stations from tracked callsign packets
   - Fetches latest position for each digipeater/iGate
   - Properly displays complete RF path visualization with station positions

Technical details:
- Added Process.alive? checks before sending messages
- Fixed resource cleanup in terminate functions
- Added proper error handling to File.read operations
- Implemented safe wrapper for ShutdownHandler health checks
- All changes ensure the app continues running even when errors occur

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 11:56:00 -05:00
d8ea2b785f
Fix ShutdownHandler health check errors
- Add safe check for ShutdownHandler process existence
- Handle case where ShutdownHandler might not be started yet
- Prevent GenServer.call errors in health checks
- Use Process.whereis and Process.alive? to verify process state

This fixes Sentry errors about 'no process' when health checks
try to call ShutdownHandler.shutting_down?()

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 08:19:27 -05:00
9c61e3c374
Fix TelemetryMetricsPrometheus buckets configuration properly
- Add reporter_options with buckets to each distribution metric
- Remove incorrect top-level buckets configuration
- Define buckets array once and reuse for all distribution metrics

The TelemetryMetricsPrometheus library requires buckets to be specified
in reporter_options for each individual distribution metric, not at the
reporter configuration level.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 14:24:25 -05:00
9b1fd942c6
Fix TelemetryMetricsPrometheus distribution buckets configuration
- Move buckets configuration from individual metrics to reporter level
- Configure default buckets for all distribution metrics in reporter options
- Remove buckets from individual metric definitions to prevent ArgumentError
- This fixes the pod crash due to "missing required buckets property" error

The TelemetryMetricsPrometheus library requires buckets to be configured
at the reporter level rather than per-metric for distribution types.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 14:16:46 -05:00
5f783894a4
Add PostgreSQL and database pool metrics to telemetry
- Create DatabaseMetrics module to collect PostgreSQL statistics
- Add metrics for database connection pool (size, idle, busy, queue)
- Add PostgreSQL connection metrics (active, idle, waiting)
- Add packets table metrics (size, row counts, operations)
- Add query performance metrics from pg_stat_statements
- Create enhanced Grafana dashboard with database visualizations
- Configure periodic measurements to collect metrics every 10 seconds

This provides comprehensive database monitoring including:
- Connection pool health and utilization
- PostgreSQL connection states and bottlenecks
- Table growth and dead tuple ratios
- Query performance statistics
- Database and table sizes

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 14:13:17 -05:00
276dc9436b
Fix 'metric type summary is unsupported' warnings
Convert unsupported summary metrics to supported types:
- Use distribution() for latency/duration measurements with appropriate buckets
- Use last_value() for current state metrics (VM memory, queue lengths)
- Use counter() for cumulative metrics (packet counts, errors)

This eliminates the TelemetryMetricsPrometheus warnings about unsupported
summary metric types while maintaining proper metric semantics.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 14:08:27 -05:00
c509500320
format 2025-07-27 13:57:33 -05:00
6fdd39dc0e
Fix CI test failures and configuration issues
- Simplify APRS_PORT parsing to avoid syntax errors
- Disable Exq properly in test environment to prevent startup issues
- Disable Prometheus telemetry in test mode to avoid port conflicts
- Update telemetry module to conditionally start Prometheus metrics

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 13:51:43 -05:00
080dac3813
Improve status page cluster information display
- Remove APRS server port from status page server field
- Add comprehensive cluster node list to cluster status section
- Highlight leader node with primary badge and indicator dot
- Display all cluster nodes in a responsive flex layout
- Include all_nodes field in cluster_info for complete node visibility

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 12:53:19 -05:00
8ddcb8000f
Fix Leaflet library loading race condition and enable Redis-free development
- Fix race condition in map bundle loading by waiting for script to load before initializing map
- Ensure Leaflet is properly exposed as window.L in map bundle
- Add Leaflet availability check in map initialization function
- Disable Exq background jobs in development environment to remove Redis dependency
- Remove invalid TelemetryMetricsPrometheus plug from endpoint
- Application now starts successfully without Redis in development mode

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 12:20:23 -05:00
863000d6b8
Add Prometheus metrics support to APRS application
- Add telemetry_metrics_prometheus dependency
- Configure Prometheus exporter in telemetry module
- Add metrics endpoint to application endpoint
- Expose /metrics for Prometheus scraping

This enables the application to be monitored by the Prometheus stack.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-27 11:44:11 -05:00
eeeebef34e
Fix PgBouncer database connection and graceful shutdown
- Add SKIP_DB_CREATE env var to skip database creation when using PgBouncer
- Fix redis_children() to return proper child specs
- Implement zero-downtime graceful shutdown without user notifications
- Add health check plug for Kubernetes probes
- Remove user-facing shutdown notifications for seamless deployments

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 15:51:06 -05:00
226a53bf61
Add Redis-based distributed caching and rate limiting
This commit introduces a dual-mode caching and rate limiting system that can use
either Redis (for distributed deployments) or ETS/Cachex (for single-node setups).

Key changes:
- Add Cache abstraction layer that automatically switches between Redis and Cachex
- Implement RedisCache module with full distributed cache functionality
- Implement RedisRateLimiter with sliding window algorithm for accurate rate limiting
- Add RateLimiterWrapper to provide unified API for both implementations
- Update application startup to conditionally use Redis when REDIS_URL is set
- Migrate all cache operations to use the new Cache abstraction
- Support graceful fallback to ETS-based solutions when Redis is unavailable

The system automatically detects Redis availability via REDIS_URL environment
variable and switches between implementations without code changes. This enables
proper distributed caching and rate limiting in Kubernetes deployments while
maintaining backward compatibility for development environments.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 15:29:38 -05:00
c85b907ab7
Move Redis PubSub config to runtime initialization
- Remove static Redis PubSub configuration from runtime.exs
- Add dynamic pubsub_config/0 function in application.ex
- Configure PubSub adapter based on cluster_enabled and REDIS_URL at startup
- Ensures proper initialization order for distributed deployments

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-26 10:10:55 -05:00
Graham McIntire
a4efe82981
Merge pull request #33 from aprsme/add-last-update-display
Add real-time last update display to map sidebar
2025-07-25 14:29:00 -05:00
7b8b417673
Address PR feedback: Add defensive handling and documentation
- Add nil handling for @last_update_at in template with fallback message
- Add documentation comments explaining timestamp update logic
- Improve code clarity based on review feedback

Addresses suggestions from automated PR review.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 14:26:58 -05:00
d42e15c159
Add real-time last update display to map sidebar
This adds a "Last Update" section below the navigation that shows:
- Relative time ("less than a minute ago")
- Exact UTC timestamp ("2025-07-25 19:04 UTC")

Updates automatically whenever LiveView receives packet updates or map bounds changes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 14:18:22 -05:00
7da700f444
Fix all failing tests
- Fixed GeoUtils.significant_movement? test: Updated test coordinates
  to actually move 55 meters (over 50m threshold) instead of 11 meters

- Fixed MapLive movement notification tests: Changed packet maps to use
  atom keys instead of string keys to match expected format

- Fixed JavaScript URL interpolation in layout template: Replaced
  incorrect HEEx syntax with static paths for vendor bundles

- Made movement tests more resilient by using receive blocks instead
  of assert_push_event for better reliability in test environment

All 376 tests now pass successfully with 0 failures.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 14:14:04 -05:00
f7cf0d0d47
Implement advanced JavaScript optimization with conditional loading
- Created minimal, tree-shaken vendor bundles:
  * Core bundle (3KB): Always loaded utilities and theme management
  * Map bundle (255KB): Leaflet + plugins, loaded only for map pages
  * Chart bundle (205KB): Chart.js, loaded only for chart pages
  * Date adapter (50KB): Chart.js time scale support
- Total optimized size: 513KB JS + 12KB CSS (was 547KB + 17KB)
- Implemented conditional loading via VendorLoader utility
- Bundle loading triggered by LiveView hooks when needed
- Fixed Chart.js date adapter dependency loading
- Eliminated unused JavaScript from being sent to browsers
- Created separate esbuild configs for each optimized bundle

Performance improvements:
- Map pages: Core + Map bundles = 258KB (47% of original)
- Chart pages: Core + Chart + Adapter = 258KB (47% of original)
- Other pages: Core bundle only = 3KB (0.5% of original)
- Reduced initial page load by 94% for non-map/chart pages

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 14:05:18 -05:00
f4bc55ed39
Optimize JavaScript delivery by creating local vendor bundles
- Download external libraries locally to reduce CDN dependencies
- Create combined vendor bundles for JavaScript and CSS
- Replace CDN script tags with local bundles in layout template
- Add build scripts for vendor bundle creation and optimization
- Configure esbuild to process vendor bundles separately
- Total bundle size: ~512KB JS + 17KB CSS (optimized)

Benefits:
- Reduced external requests and CDN dependencies
- Better caching control and reliability
- Eliminated CORS and SRI integrity issues
- Faster page loads with bundled resources

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 13:56:12 -05:00
410f6b8336
Fix overlapping marker spiderfier path and CDN
Use the correct entry point from package.json (dist/oms.js) instead
of oms.min.js, and switch back to unpkg with the verified working
path to resolve content corruption issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 13:42:46 -05:00
2f82d05008
Fix MIME type issue with overlapping marker spiderfier
Switch from unpkg.com to jsdelivr.net CDN to resolve MIME type
mismatch error. The unpkg CDN was serving the JavaScript file
with 'text/plain' content type instead of 'application/javascript'.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 13:34:19 -05:00
0f26cd7e22
Add cluster-aware APRS-IS status checking
Both /status and /status.json endpoints now show cluster-wide APRS-IS
connection status instead of just local node status.

Features:
- get_cluster_aprs_status() checks all nodes in cluster via RPC
- Returns status from any connected node (usually the leader)
- Shows cluster info: total nodes, connected nodes, leader node
- Graceful fallback to local status in non-clustered mode
- Enhanced status page UI with cluster information display

This ensures status pages accurately reflect the true APRS-IS
connection state across the entire cluster.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 13:10:23 -05:00
6c5714a49c
Fix right overlay panel cutoff issue
- Added missing CSS for slideover panel with proper width and positioning
- Fixed overflow issues by adding box-sizing: border-box
- Adjusted Track button padding from px-6 to px-4 to prevent cutoff
- Added whitespace-nowrap and flex-shrink-0 to prevent button text wrapping
- Ensured proper panel width of 352px on desktop with transform animations

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 10:12:27 -05:00
cd5d8cc33f
Fix right sidebar overlay cutoff issue
- Added base CSS styles for slideover panel with proper positioning
- Increased right padding (pr-8) in content area to prevent button cutoff
- Set overflow to hidden on panel container to prevent scrollbar issues
- Ensures Track button and other content are fully visible

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 10:11:30 -05:00
935b70b4b1
Fix rate limiter header check bug
Fixed ArgumentError in rate limiter by replacing cond statement with
proper case pattern matching. The cond was incorrectly evaluating
assignment expressions as truthy values instead of checking if headers
were empty lists.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-25 09:30:59 -05:00
fb34553fe9
Add Cloudflare header support for real client IP
- Updated IP geolocation to check CF-Connecting-IP header first
- Added support for X-Real-IP header as additional fallback
- Updated rate limiter to use same header priority for consistency
- Headers checked in order: CF-Connecting-IP > X-Forwarded-For > X-Real-IP > remote_ip

This ensures proper client IP detection when the site is behind Cloudflare.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-24 20:29:48 -05:00
206adb6b7d
back to raster 2025-07-24 12:02:28 -05:00
ae8c74afbe
try vector tiles 2025-07-24 10:54:31 -05:00
688fc6ba95
Implement mobile web optimization and fix sidebar clipping
- Add comprehensive mobile viewport meta tags for better mobile experience
- Implement touch gesture support with long-press to show station info
- Create mobile-specific CSS with touch target optimization (44px minimum)
- Add safe area insets support for notched devices
- Position zoom controls at bottom-right on mobile for thumb accessibility
- Optimize map performance on mobile with canvas renderer
- Fix desktop sidebar toggle button clipping when slideover is open
- Improve responsive design for slideover panels and popups
- Add landscape mode and high DPI screen optimizations

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 14:31:49 -05:00
1e51d58af5
Fix missing zoom controls by adding vendor.css
The zoom controls were not displaying because the vendor.css file
containing Leaflet styles was not being included in the HTML template.
Added the missing stylesheet link to load vendor.css.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 13:26:01 -05:00
44ae82eb9b
Improve map tile loading reliability
- Add retry logic with exponential backoff for failed tiles (up to 3 attempts)
- Configure tile layer with optimized settings (keepBuffer, updateInterval)
- Add support for alternative tile providers (OSM DE, CartoDB)
- Update CSP to allow alternative tile providers
- Add error tile placeholder to prevent broken images

This should help with partial tile loading issues by retrying failed requests
and providing fallback options.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 13:22:45 -05:00
63a87f3be1
Fix Docker build JavaScript import errors with vendor bundle
Fixed the esbuild errors during Docker build where npm packages couldn't
be resolved. The issue was caused by node_modules being excluded from
the Docker build context in .dockerignore.

Implemented a proper vendor bundle strategy following Phoenix 1.7+ patterns:

- Created vendor.js that bundles all npm dependencies (Leaflet, Chart.js,
  topbar) and their CSS files into a single pre-built bundle
- Added vendor esbuild profile in config.exs with proper CSS/image loaders
- Updated mix.exs to build vendor bundle before app bundle in assets.deploy
- Modified JavaScript imports to use globally loaded libraries from vendor
  bundle instead of importing from node_modules
- Added vendor.js script tag to root.html.heex before app.js

This approach avoids Docker build failures by pre-bundling all vendor
dependencies, eliminating the need for node_modules during production builds.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 12:32:39 -05:00
b4206b63d1
Increase GPS drift threshold from 15m to 50m
Fixed issue where stationary stations like K5SGD-D were showing multiple
positions due to minor GPS accuracy variations. The previous 15-meter
threshold was too strict for typical GPS drift which can vary 5-30 meters.

- Increased default threshold in GeoUtils from 10m to 50m
- Updated packet processor to use 50m threshold for movement detection
- Verified with K5SGD-D showing ~24m variations that are now ignored

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 11:23:37 -05:00
9b034cd211
vendor all js 2025-07-21 11:09:14 -05:00
7dc6c616a2
Bundle all vendor JavaScript files locally instead of loading from CDNs
This change eliminates all external CDN dependencies by bundling JavaScript
libraries locally using esbuild. All vendor files are now served from the
application's own assets, improving reliability and privacy.

Changes:
- Created assets/js/vendor.js to bundle all external libraries
- Added vendor bundle configuration to esbuild in config files
- Updated all templates to use local vendor.js instead of CDN scripts
- Created download-vendor-files.sh script to fetch vendor libraries
- Added vendor files to .gitignore (they're downloaded during setup)
- Updated CSS imports to include vendor stylesheets
- Added vendor build step to assets.deploy mix task

Bundled libraries:
- Leaflet 1.9.4 (mapping library)
- Leaflet.heat 0.2.0 (heatmap plugin)
- Leaflet.markercluster 1.5.3 (marker clustering)
- OverlappingMarkerSpiderfier 0.2.6 (overlapping marker handling)
- Chart.js 4.5.0 (charting library)
- chartjs-adapter-date-fns 3.0.0 (date adapter for Chart.js)

All functionality tested and working correctly with local bundles.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 09:52:39 -05:00
aa168f8dcd
Add URL parameter handling for trail duration and historical data
Implemented URL persistence for map settings to preserve user preferences:

- Added `trail` parameter for trail duration (1, 6, 12, 24, 48, 168 hours)
- Added `hist` parameter for historical data hours (1, 3, 6, 12, 24 hours)
- Parameters only appear in URL when different from default value (1 hour)

Changes:
- Updated handle_url_update to include trail and hist parameters
- Modified mount to parse these parameters on page load
- Updated handle_params to parse parameters on URL changes
- Added update_url_with_current_state helper function
- Modified update_trail_duration and update_historical_hours event handlers
  to update URL when settings change
- Fixed finalize_mount_assigns to preserve parsed values

This allows users to share or bookmark URLs with specific trail duration
and historical data settings, improving the user experience.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 09:14:46 -05:00