From 72bb37ffe5dfa4a5a834ecc23b0fdaa9e5dc47e5 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Sun, 1 Mar 2026 22:46:05 +0800 Subject: [PATCH 1/2] fix: correct sidebar LAN detection for public access Separate Tailscale IP detection from true LAN detection so public internet users routed through VPS no longer get non-routable port links in the sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) --- dashboard/backend/config.py | 12 +++++++++++- dashboard/backend/main.py | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/dashboard/backend/config.py b/dashboard/backend/config.py index 16140c0..80aee94 100644 --- a/dashboard/backend/config.py +++ b/dashboard/backend/config.py @@ -48,9 +48,10 @@ CHAT_SUMMARY_DB = os.environ.get("CHAT_SUMMARY_DB", "/app/data/chat-summarizer/c OPENCLAW_GATEWAY_TOKEN = os.environ.get("OPENCLAW_GATEWAY_TOKEN", "") # Networking configs -LAN_IP_RANGES = os.environ.get("LAN_IP_RANGES", "192.168.31.0/24,100.64.0.0/10").split(",") +LAN_IP_RANGES = os.environ.get("LAN_IP_RANGES", "192.168.31.0/24").split(",") def is_lan_ip(ip_str: str) -> bool: + """Check if IP is in local LAN (not Tailscale)""" import ipaddress try: ip_obj = ipaddress.ip_address(ip_str) @@ -61,6 +62,15 @@ def is_lan_ip(ip_str: str) -> bool: except ValueError: return False +def is_tailscale_ip(ip_str: str) -> bool: + """Check if IP is in Tailscale CGNAT range (100.64.0.0/10)""" + import ipaddress + try: + ip_obj = ipaddress.ip_address(ip_str) + return ip_obj in ipaddress.ip_network("100.64.0.0/10") + except ValueError: + return False + TRUSTED_PROXIES = os.environ.get("TRUSTED_PROXIES", "127.0.0.1,::1,172.16.0.0/12").split(",") def is_trusted_proxy(ip_str: str) -> bool: diff --git a/dashboard/backend/main.py b/dashboard/backend/main.py index 8d22068..569f389 100644 --- a/dashboard/backend/main.py +++ b/dashboard/backend/main.py @@ -134,8 +134,19 @@ async def client_ip(request: Request): forwarded = request.headers.get("x-forwarded-for", "").split(",")[0].strip() if forwarded: ip = forwarded - import config - lan = config.is_lan_ip(ip) if not ip.startswith("100.") else config.is_lan_ip(ip) and _router_reachable() + + # LAN detection logic: + # - True LAN IP (192.168.31.x) → lan=True + # - Tailscale IP with router reachable → lan=True (user at home with Tailscale) + # - Tailscale IP without router reachable → lan=False (remote via VPS proxy) + # - Other IPs → lan=False (public internet) + if config.is_lan_ip(ip): + lan = True + elif config.is_tailscale_ip(ip): + lan = _router_reachable() + else: + lan = False + return {"lan": lan} def _router_reachable(): From bd2a166253a14fc7f356bed9a9821f2a6e1f4c77 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Sun, 1 Mar 2026 22:55:37 +0800 Subject: [PATCH 2/2] fix: use hybrid sidebar links for LAN and public access Keep LAN/Tailscale-home behavior for NAS-targeted links while converting remote :8443 URLs to standard HTTPS on public access so external links resolve correctly. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../frontend/src/components/Sidebar.svelte | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/dashboard/frontend/src/components/Sidebar.svelte b/dashboard/frontend/src/components/Sidebar.svelte index b4dca40..7242961 100644 --- a/dashboard/frontend/src/components/Sidebar.svelte +++ b/dashboard/frontend/src/components/Sidebar.svelte @@ -169,9 +169,28 @@ handleDragEnd(); } + function toPublicHttpsUrl(href) { + if (typeof window === "undefined") return href; + if (!href) return href; + + const isStandardHttps = location.protocol === "https:" && (!location.port || location.port === "443"); + if (!isStandardHttps) return href; + + try { + const url = new URL(href); + if (url.protocol === "https:" && url.port === "8443") { + url.port = ""; + return url.toString(); + } + return href; + } catch { + return href; + } + } + function extHref(svc) { if (lanReachable && svc.port) return `http://${LAN_IP}:${svc.port}`; - if (svc.remoteHref) return svc.remoteHref; + if (svc.remoteHref) return lanReachable ? svc.remoteHref : toPublicHttpsUrl(svc.remoteHref); if (svc.port) { const host = /^\d+\.\d+\.\d+\.\d+$/.test(location.hostname) ? location.hostname : TS_IP; return `http://${host}:${svc.port}`;