diff --git a/dashboard/backend/config.py b/dashboard/backend/config.py index f4211c6..a1341f1 100644 --- a/dashboard/backend/config.py +++ b/dashboard/backend/config.py @@ -22,6 +22,10 @@ ADMIN_PASSWORD_HASH = os.environ.get("ADMIN_PASSWORD_HASH", "") TOTP_SECRET = os.environ.get("TOTP_SECRET", "") # SSH terminal +# NOTE: The default values below are for development only. +# In production, override these via environment variables: +# SSH_HOST, SSH_USER, VPS_SSH_HOST, VPS_SSH_USER, +# CADDY_VPS_SSH_HOST, CADDY_VPS_SSH_USER, MAC_SSH_HOST, MAC_SSH_USER SSH_HOST = os.environ.get("SSH_HOST", "host.docker.internal") SSH_USER = os.environ.get("SSH_USER", "zjgump") SSH_KEY_PATH = os.environ.get("SSH_KEY_PATH", "/app/ssh/id_ed25519") @@ -51,6 +55,26 @@ WEBAUTHN_ORIGINS = os.environ.get("WEBAUTHN_ORIGINS", "https://nas.jimmygan.com, # CORS CORS_ORIGINS = os.environ.get("CORS_ORIGINS", "https://nas.jimmygan.com,https://nas.jimmygan.com:8443").split(",") +# Dashboard public URL (used in email templates, etc.) +DASHBOARD_URL = os.environ.get("DASHBOARD_URL", "https://nas.jimmygan.com") + +# External service URLs (used by frontend sidebar) +EXTERNAL_SERVICES = { + "navidrome": os.environ.get("NAVIDROME_URL", "https://music.jimmygan.com:8443"), + "jellyfin": os.environ.get("JELLYFIN_URL", "https://media.jimmygan.com:8443"), + "audiobookshelf": os.environ.get("AUDIOBOOKSHELF_URL", "https://books.jimmygan.com:8443"), + "immich": os.environ.get("IMMICH_URL", "https://photos.jimmygan.com:8443"), + "gitea_web": os.environ.get("GITEA_WEB_URL", "https://git.jimmygan.com:8443"), + "stirling_pdf": os.environ.get("STIRLING_PDF_URL", "https://pdf.jimmygan.com:8443"), + "vaultwarden": os.environ.get("VAULTWARDEN_URL", "https://vault.jimmygan.com:8443"), + "n8n": os.environ.get("N8N_URL", "https://n8n.jimmygan.com:8443"), + "speedtest": os.environ.get("SPEEDTEST_URL", "https://speed.jimmygan.com:8443"), +} + +# Network addresses (used by frontend for LAN/Tailscale direct access) +LAN_IP = os.environ.get("LAN_IP", "192.168.31.222") +TS_IP = os.environ.get("TS_IP", "100.78.131.124") + # Chat Summary CHAT_SUMMARY_DB = os.environ.get("CHAT_SUMMARY_DB", "/app/data/chat-summarizer/chat_summary.db") diff --git a/dashboard/backend/routers/system.py b/dashboard/backend/routers/system.py index f1a6158..6a6da9e 100644 --- a/dashboard/backend/routers/system.py +++ b/dashboard/backend/routers/system.py @@ -149,3 +149,13 @@ def openclaw_token(request: Request): if not config.is_lan_ip(ip): raise HTTPException(status_code=403, detail="Access denied") return {"token": OPENCLAW_GATEWAY_TOKEN} + + +@router.get("/external-services") +def external_services(): + """Return external service URLs and network config for the frontend sidebar.""" + return { + "lan_ip": config.LAN_IP, + "ts_ip": config.TS_IP, + "services": config.EXTERNAL_SERVICES, + } diff --git a/dashboard/backend/services/email_service.py b/dashboard/backend/services/email_service.py index 7a60f3c..b9dbddb 100644 --- a/dashboard/backend/services/email_service.py +++ b/dashboard/backend/services/email_service.py @@ -8,6 +8,8 @@ import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText +import config + logger = logging.getLogger(__name__) # Email configuration from environment @@ -64,7 +66,7 @@ Status: {task['status']} Description: {task.get('description', 'No description')} -View task: https://nas.jimmygan.com/?page=opc +View task: {config.DASHBOARD_URL}/?page=opc """ html_body = f""" @@ -79,7 +81,7 @@ View task: https://nas.jimmygan.com/?page=opc

Description:

{task.get('description', 'No description')}

-

View Task

+

View Task

""" @@ -107,7 +109,7 @@ Proposed Actions: {actions_text} Please review and approve in the OPC dashboard: -https://nas.jimmygan.com/?page=opc +{config.DASHBOARD_URL}/?page=opc """ html_body = f""" @@ -122,7 +124,7 @@ https://nas.jimmygan.com/?page=opc -

Review & Approve

+

Review & Approve

""" @@ -140,7 +142,7 @@ Agent: {agent_name} Task: {task['title']} Actions Executed: {actions_count} -View details: https://nas.jimmygan.com/?page=opc +View details: {config.DASHBOARD_URL}/?page=opc """ html_body = f""" @@ -150,7 +152,7 @@ View details: https://nas.jimmygan.com/?page=opc

Agent: {agent_name}

Task: {task['title']}

Actions Executed: {actions_count}

-

View Details

+

View Details

""" diff --git a/dashboard/frontend/src/components/Sidebar.svelte b/dashboard/frontend/src/components/Sidebar.svelte index c0a58c1..d73668f 100644 --- a/dashboard/frontend/src/components/Sidebar.svelte +++ b/dashboard/frontend/src/components/Sidebar.svelte @@ -27,14 +27,22 @@ { id: "security", label: "Security", icon: "shield" }, ]; - const LAN_IP = "192.168.31.222"; - const TS_IP = "100.78.131.124"; + let lanIp = $state("192.168.31.222"); + let tsIp = $state("100.78.131.124"); let lanReachable = $state(false); + let serviceUrls = $state({}); if (typeof window !== "undefined") { fetch("/api/client-ip").then(r => r.json()).then(d => { if (d.lan) lanReachable = true; }).catch(() => {}); + + // Fetch external service URLs from backend config + fetch("/api/system/external-services").then(r => r.json()).then(d => { + if (d.lan_ip) lanIp = d.lan_ip; + if (d.ts_ip) tsIp = d.ts_ip; + if (d.services) serviceUrls = d.services; + }).catch(() => {}); } const defaultMedia = [ @@ -197,10 +205,15 @@ } function extHref(svc) { - if (lanReachable && svc.port) return `http://${LAN_IP}:${svc.port}`; - if (svc.remoteHref) return lanReachable ? svc.remoteHref : toPublicHttpsUrl(svc.remoteHref); + // Look up service URL from fetched config by label (lowercased, underscored) + const key = svc.label ? svc.label.toLowerCase().replace(/\s+/g, "_") : ""; + const configUrl = serviceUrls[key]; + const remoteHref = configUrl || svc.remoteHref; + + if (lanReachable && svc.port) return `http://${lanIp}:${svc.port}`; + if (remoteHref) return lanReachable ? remoteHref : toPublicHttpsUrl(remoteHref); if (svc.port) { - const host = /^\d+\.\d+\.\d+\.\d+$/.test(location.hostname) ? location.hostname : TS_IP; + const host = /^\d+\.\d+\.\d+\.\d+$/.test(location.hostname) ? location.hostname : tsIp; return `http://${host}:${svc.port}`; } return svc.href;