Fix unreachable mobile Settings link and raw error tracebacks
All checks were successful
Deploy / deploy (push) Successful in 2m6s

Settings lived inside .header-right, which is hidden below 600px,
leaving no way to reach it on mobile. It's now a standalone 44x44px
button next to the title.

Dashboard error cards showed raw Python/urllib3 exception strings.
Added a friendly_error() filter that classifies the failure (timeout,
connection refused, DNS, auth, 4xx/5xx, TLS) into plain-English copy,
with the raw string still available behind a details disclosure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Barely Removable 2026-08-25 23:59:07 -07:00
parent 9804283396
commit 2e1f8efa33
2 changed files with 133 additions and 16 deletions

35
app.py
View file

@ -32,6 +32,41 @@ def save_settings(data):
app = Flask(__name__)
app.secret_key = os.environ.get("SECRET_KEY") or secrets.token_hex(32)
def friendly_error(raw, service=""):
"""Turn a raw exception string into a short, human-readable cause.
The service dicts carry str(e) from requests/urllib3, which is useful for
debugging but unreadable on the dashboard. Templates render this instead
and keep the raw string behind a details disclosure.
"""
s = str(raw or "")
low = s.lower()
name = service or "The service"
if low.strip() == "timeout":
return f"{name} took too long to answer and was dropped at the 5s page deadline."
if "connecttimeout" in low or "timed out" in low or "read timed out" in low:
return f"{name} didn't respond — the connection timed out."
if "connection refused" in low or "newconnectionerror" in low:
return f"{name} refused the connection — check that it's running and the port is right."
if "name or service not known" in low or "nameresolution" in low or "failed to resolve" in low:
return f"{name}'s hostname couldn't be resolved — check the host setting."
if "no route to host" in low or "network is unreachable" in low:
return f"{name} is unreachable from this container — check the network."
if "401" in s or "unauthorized" in low or "403" in s or "forbidden" in low:
return f"{name} rejected the API key."
if "404" in s or "not found" in low:
return f"{name} returned 404 — check the URL or base path."
if any(code in s for code in ("500", "502", "503", "504")):
return f"{name} returned a server error."
if "ssl" in low or "certificate" in low:
return f"{name}'s TLS handshake failed — check the certificate settings."
return f"{name} couldn't be reached."
app.jinja_env.filters["friendly_error"] = friendly_error
app.config["SESSION_COOKIE_PATH"] = "/"
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"