Stop sending further API requests mid-batch once the backoff is
triggered, saving remaining missing IPs as empty placeholders
immediately instead of waiting for them to timeout one-by-one.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After _GEO_MISS_THRESHOLD (2) consecutive large all-empty API responses,
assume ip-api.com is unreachable and enter a 5-minute backoff.
During backoff all missing IPs are immediately saved as empty IPGeoCache
placeholders so they are not re-queued. Resets automatically when the
backoff expires and any result is returned.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add WanGeoStatsView (api/wan/geo/) returning country choropleth +
bubble data for WAN source IPs using IPGeoCache lat/lon
- Add wanGeoMap (420px) in dashboard between ports chart and live table:
red colour scale by country, rose bubbles sized by attempt count,
top-5 country legend
- Rewrite _geo_worker_loop to drain up to 200 queue items per cycle,
bulk DB cache check, batch API calls (100 IPs per request) instead
of 1 request per IP — fixes ip-api.com timeout pile-up under scan
volumes
- Add _geo_skip in-memory set: IPs that return no geo data get an
empty IPGeoCache placeholder saved to DB so they are never retried;
skip set also prevents re-queuing known-empty IPs during flush
- Remove _fetch_geo / _enrich_dns_geo / _enrich_wan_geo helpers
(logic consolidated into the new batch worker)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove SO_REUSEADDR from UDP socket so only one gunicorn worker binds
port 5514 (second worker intentionally gets EADDRINUSE and skips)
- Add print() fallback for bind confirmation/errors so startup is visible
even when Django logging config is not fully set up
- Fix duplicate LOGGING dict in settings.py: second assignment was
overriding the first and removing the root logger handler, silently
swallowing all routermon/core startup logs
- Add sys.stderr fallback in core/apps.py ready() for startup exceptions
- Fix JS SyntaxError: {{ top_domains_json }} needs |safe filter since
Django auto-escapes quotes to " inside script tags
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>