Adds a "Custom Range" sweep type that lets users specify start/end MHz
instead of using a fixed preset. Useful in dense RF environments where
a full or standard sweep returns too many signals and causes slowdown.
UI shows start/end MHz inputs when "Custom Range" is selected. Range is
validated (0 < start < end ≤ 6000 MHz) before the sweep starts.
Backend threads the ranges through to _scan_rf_signals(), which already
supports arbitrary frequency bands.
Closes#172
/health now includes sdr_claims: a dict mapping 'sdr_type:device_index'
to the mode currently using that device (e.g. {"rtlsdr:0": "pager"}).
Empty when no devices are in use.
/devices/status already existed and returns the full device list with
in_use/used_by per device — documented in the issue response.
Closes#158
Adds three new icon shapes (widebody, bizjet, turboprop) to the existing
set (jet, prop, helicopter, military, glider), giving 8 distinct silhouettes.
Classification covers common ICAO type codes: widebodies (744, 777, A380 etc.),
business jets (Citation, Gulfstream, Learjet etc.), turboprops (ATR, DH8 etc.),
and light GA piston aircraft.
Hover tooltip now shows aircraft type description (e.g. "Airbus A320-200")
when available from the aircraft DB, in addition to callsign and altitude.
Closes#201
Strong passes at 40 dB (the previous default) cause RTL-SDR ADC clipping,
producing a distorted IQ stream that SatDump cannot lock onto. 30 dB is
a safer starting point that still captures weak passes cleanly.
Also adds a UI hint below the gain control explaining the saturation issue.
Closes#185
Remove the split fast-path in doLocateHandoff that called BtLocate.handoff()
directly when the module was already loaded. That path relied on handoff()
internally calling switchMode, causing a double switchMode in the lazy-load
path and no guaranteed mode switch in the fast path.
Now doLocateHandoff always calls switchMode('bt_locate') first (lazy-loading
script/styles as needed), then calls BtLocate.handoff() in .then(). Removed
the redundant switchMode call from BtLocate.handoff() since the caller owns
the mode transition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the requestAnimationFrame loop in proximity-radar.js with a
CSS @keyframes rotation on .bt-radar-sweep, mirroring the WiFi radar
pattern. Adds two trailing arc paths for a glow effect and updates
setPaused() to toggle animationPlayState instead of the rAF flag.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace static channel bar chart and security dots with a scrolling
2.4 GHz channel heatmap (up to 10 scan snapshots) and an SVG donut
security ring showing WPA2/WPA3/WEP/Open network distribution.
Replaces the 7-column <table> network list with flex div rows featuring
two-line layout (SSID + security badges on top, signal bar + meta on
bottom), coloured left-border threat indicators, and new sort controls.
Renames selectedNetwork → selectedBssid and updateNetworkTable → renderNetworks throughout wifi.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
config.VERSION was not updated when the v2.26.13 tag was created,
causing the update checker to always report an update available on
fresh installs and git pulls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move aircraft_db.json and aircraft_db_meta.json from the project root
to data/adsb/ so they survive container restarts and rebuilds. Add
matching volume mount to both Docker Compose profiles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mounting ./data:/app/data caused the host directory to shadow the
entire /app/data Python package, making modules like data.oui
unavailable and crashing gunicorn on startup. Mount only the three
runtime output subdirectories instead.
Fixes#200
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Access module-level _sweep_running, _current_sweep_id, and tscm_queue
via explicit package import to avoid UnboundLocalError from closure
variable shadowing in route handlers
- Remove orphaned tscmProgressBar.style.width assignment in index.html
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix deep scan with 'All bands' never scanning 5GHz: band='all' now
correctly passes --band abg to airodump-ng (previously no flag was
added, causing airodump-ng to default to 2.4GHz-only)
- Fix APs first seen without channel info permanently stuck at
band='unknown': _update_access_point now backfills channel, frequency,
and band when a subsequent observation resolves the channel
- Fix legacy /wifi/scan/start combining mutually exclusive --band and -c
flags: --band is now only added when no explicit channel list is given,
and the interface is always placed as the last argument
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When using a remote SBS feed, no local SDR is needed. The pre-flight
device conflict check was running regardless and stopping whichever
mode had the selected SDR device claimed — even though ADS-B remote
mode never touches a local SDR. Skip the conflict check when remoteConfig is set.
check_tools() was using cmd_exists on 'auto_rx.py' which fails because
it's never in PATH — installed to /opt/radiosonde_auto_rx/. Now uses
the same file-based check as tool_is_installed(), consistent with
health check and status view.
Precise calculation showed mission-drawer (~1100px) was 213px taller
than command-rail content (~887px), leaving 213px of empty background
at the bottom of the right column.
Three targeted reductions to mission-drawer height (~234px total):
- drawer-actions: stacked 1-column → 3-column row (-76px)
- drawer-list: max-height 240px → 180px (-40px)
- drawer-info-grid: 1-column → 2-column for Quick Info (-118px)
Mission-drawer drops to ~866px, command-rail (~887px) now drives the
primary-layout row height — gap closes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The command-rail's 1fr last row caused the sky view panel to fill all
remaining column height (driven by the tall mission-drawer), showing a
large empty bordered space below the pass data strip.
Switch to all-auto rows with align-content: start so each panel is
exactly as tall as its content — the open background below the column
looks intentional rather than a panel with dead space inside it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove 'METEOR-M2' (NORAD 40069) from WEATHER_SAT_KEYS — it has no
entry in WEATHER_SATELLITES and no dropdown option, so the Capture
button was silently scheduling against the wrong satellite
- Dispatch a 'change' event after setting satSelect.value in preSelect()
and startPass() so any UI listeners (frequency display, mode info)
update correctly when the satellite is set programmatically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1fr in the grid row caused the panel to fill the entire remaining page
height on mobile (~1000px+), leaving large gaps around the centred content.
max-height: min(55vh, 520px) keeps it proportionate on any screen size.
Also switch to justify-content: flex-start so the canvas+strip pack at
the top rather than floating in the middle of a large void.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fills the empty space below the sky view circle with a compact
three-column AOS / TCA / LOS readout (time + azimuth/elevation)
and a duration + max elevation footer line.
Populated by drawPolarPlot() when a pass is selected; shows a
placeholder prompt otherwise.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setting both width:100% and height:100% made CSS ignore aspect-ratio,
stretching the drawing buffer non-uniformly into the tall container.
Fixed by keeping only width:100% + max-height:100% so aspect-ratio:1/1
clamps the height and the element stays square.
Draw functions now use canvas.offsetWidth for the square buffer size.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- command-rail last row changed from minmax(260px, 340px) to 1fr so the
polar plot expands to fill whatever vertical space remains after the
Next Pass and Live Telemetry panels
- polar-container made flex-column so panel-content can grow with flex: 1
- #polarPlot width/height 100% with aspect-ratio 1/1 — canvas fills the
available square area and stays proportional
- Remove align-items: start from the 1320px breakpoint primary-layout so
the command-rail stretches to match map height in the two-column layout
- Fix matching 2-column command-rail rows at 1320px breakpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove align-self: start from .polar-container so the grid row's
minmax(260px, 340px) height is actually respected
- Switch #polarPlot to aspect-ratio: 1/1 so the canvas is always square
- Fix both draw functions to size canvas from getBoundingClientRect on
the canvas itself (not parent) using min(width, height) for a square plot
- Remove min-height from .dashboard to prevent empty space below content
on narrow/mobile screens where stacked panels are shorter than 720px
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move _passAbortController = null to after response.json() so the retry
scheduler cannot see a false idle state mid-parse, increment
_passRequestId, and discard the in-flight response — this was causing
non-ISS satellites to show no passes intermittently
- Add _computeSlantRange() helper using 3D ECEF geometry
- Update applyTelemetryPosition to compute slant range from SSE lat/lon/
altitude, giving distance updates at 1Hz instead of 5s HTTP poll rate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two root-cause bugs causing the reported issues:
1. Tracker never sent ISS positions: _start_satellite_tracker fell back
to sat_name.replace(' ', '-').upper() as the TLE cache key when the
DB entry had null TLE lines. For 'ISS (ZARYA)' this produced
'ISS-(ZARYA)' which has no matching entry in _tle_cache (keyed as
'ISS'). ISS was silently skipped every loop tick, so no SSE positions
were ever emitted and the map marker never moved.
Fix: try _BUILTIN_NORAD_TO_KEY.get(norad_int) first before the
name-derived fallback so the NORAD-to-key mapping is always used.
2. Stale TLE pass prediction results were cached: if startup TLEs were
too old for Skyfield to find events in the 48h window, the empty
passes list was cached for 300s. A page refresh within that window
re-served the empty result, showing 'NO PASSES FOUND' persistently.
Fix: only cache non-empty pass results so the next request
recomputes once the TLE auto-refresh has populated fresh data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>