59c369c9e2
Playlist detail page's status section (and the new two-column compare view) was never rendering: the router awaited status_service.playlist_status(), but that function is plain sync, so awaiting its dict return raised "object dict can't be used in 'await' expression" on every load, silently caught and shown as a generic fetch error. Removed the erroneous await. find-fuzzy-dupes.py's --apply and --apply-pairs both deleted by a `path::` regex query even though the beets id was already available in scope -- same mixed-path-storage issue (pre/post beets-2.11-upgrade items store absolute vs. library-relative paths) dedup-library.sh already worked around by deleting via id instead. This silently failed to match for most confirmed fuzzy-audio deletions (verified: 19 of 20 in one run). Added a delete_id column to dedup_candidates, threaded through the scan/apply pipeline, and switched both delete call sites to `id:`. Dedup scans also never checked whether a pair was already sitting in the pending list, so every re-scan (including the daily schedule) added a new row for the same unreviewed duplicate -- cleaned up 38 redundant rows already in production and added a check so future scans skip a pair that's already pending. Dashboard gets a Public IP stat card (cached 10 min, fetched via ipify) as a quick confidence check that outbound traffic is actually routed through gluetun's VPN and not the home connection.
32 lines
1.2 KiB
Python
32 lines
1.2 KiB
Python
import time
|
|
|
|
import httpx
|
|
|
|
# alembic rides gluetun's network namespace (network_mode: service:gluetun)
|
|
# specifically so sldl's Soulseek traffic stays VPN-routed -- this is the
|
|
# dashboard's confidence check that it's actually working: the container's
|
|
# own outbound IP should be the VPN's, not the home connection's. Cached
|
|
# in-process (no DB row, no job history) since it's a lightweight external
|
|
# check, not something worth tracking over time.
|
|
_CACHE_TTL_SECONDS = 600
|
|
_cache: dict = {"ip": None, "checked_at": 0.0, "error": None}
|
|
|
|
|
|
def public_ip() -> dict:
|
|
now = time.time()
|
|
if _cache["ip"] is not None and now - _cache["checked_at"] < _CACHE_TTL_SECONDS:
|
|
return dict(_cache)
|
|
|
|
try:
|
|
resp = httpx.get("https://api.ipify.org", timeout=5.0)
|
|
resp.raise_for_status()
|
|
_cache["ip"] = resp.text.strip()
|
|
_cache["error"] = None
|
|
except Exception as exc:
|
|
_cache["error"] = str(exc)
|
|
# Keep the last-known-good ip (if any) rather than blanking it --
|
|
# a transient fetch failure shouldn't make the dashboard look like
|
|
# the VPN dropped when it's actually just ipify being slow.
|
|
_cache["checked_at"] = now
|
|
return dict(_cache)
|