diff --git a/app/sovran_systemsos_web/static/app.js b/app/sovran_systemsos_web/static/app.js index 2f039ab..72b0160 100644 --- a/app/sovran_systemsos_web/static/app.js +++ b/app/sovran_systemsos_web/static/app.js @@ -85,6 +85,14 @@ function escHtml(str) { .replace(/'/g, "'"); } +function linkify(str) { + const escaped = escHtml(str); + return escaped.replace( + /(https?:\/\/[^\s<]+)/g, + '$1' + ); +} + // ── Fetch wrappers ──────────────────────────────────────────────── async function apiFetch(path, options = {}) { @@ -269,7 +277,7 @@ async function loadNetwork() { if ($internalIp) $internalIp.textContent = data.internal_ip || "—"; if ($externalIp) $externalIp.textContent = data.external_ip || "—"; } catch (_) { - if ($internalIp) $internalIp.textContent = "��"; + if ($internalIp) $internalIp.textContent = "—"; if ($externalIp) $externalIp.textContent = "—"; } } @@ -310,11 +318,12 @@ async function openCredsModal(unit, name) { let html = ""; for (const cred of data.credentials) { const id = "cred-" + Math.random().toString(36).substring(2, 8); + const displayValue = linkify(cred.value); html += `
${escHtml(cred.label)}
-
${escHtml(cred.value)}
+
${displayValue}
diff --git a/app/sovran_systemsos_web/static/style.css b/app/sovran_systemsos_web/static/style.css index 62eeee2..e78c96c 100644 --- a/app/sovran_systemsos_web/static/style.css +++ b/app/sovran_systemsos_web/static/style.css @@ -780,7 +780,7 @@ button.btn-reboot:hover:not(:disabled) { justify-content: center; } .service-tile { - width: 160px; + : width: 160px; min-height: 200px; } .reboot-card { @@ -790,4 +790,18 @@ button.btn-reboot:hover:not(:disabled) { .creds-dialog { margin: 0 12px; } -} \ No newline at end of file + +/* ── Credential links ─────────────────────────────────────── */ +.creds-link { + color: #58a6ff; + text-decoration: none; + word-break: break-all; +} +.creds-link:hover { + text-decoration: underline; + color: #79c0ff; +} + + + +} diff --git a/modules/core/sovran-hub.nix b/modules/core/sovran-hub.nix index 9e60227..a0f1d1f 100644 --- a/modules/core/sovran-hub.nix +++ b/modules/core/sovran-hub.nix @@ -35,14 +35,18 @@ let ]; } { name = "LND"; unit = "lnd.service"; type = "system"; icon = "lnd"; enabled = cfg.services.bitcoin; category = "bitcoin-apps"; credentials = []; } { name = "Ride The Lightning"; unit = "rtl.service"; type = "system"; icon = "rtl"; enabled = cfg.services.bitcoin; category = "bitcoin-apps"; credentials = [ - { label = "URL"; file = "/var/lib/tor/onion/rtl/hostname"; prefix = "http://"; } + { label = "Tor Access"; file = "/var/lib/tor/onion/rtl/hostname"; prefix = "http://"; } + { label = "Local Network"; file = "/var/lib/secrets/internal-ip"; prefix = "http://"; suffix = ":3050"; } { label = "Password"; file = "/etc/nix-bitcoin-secrets/rtl-password"; } ]; } { name = "BTCPayserver"; unit = "btcpayserver.service"; type = "system"; icon = "btcpayserver"; enabled = cfg.services.bitcoin; category = "bitcoin-apps"; credentials = [ { label = "URL"; file = "/var/lib/domains/btcpayserver"; prefix = "https://"; } { label = "Note"; value = "Create your admin account on first visit"; } ]; } - { name = "Mempool"; unit = "mempool.service"; type = "system"; icon = "mempool"; enabled = cfg.features.mempool; category = "bitcoin-apps"; credentials = []; } + { name = "Mempool"; unit = "mempool.service"; type = "system"; icon = "mempool"; enabled = cfg.features.mempool; category = "bitcoin-apps"; credentials = [ + { label = "Tor Access"; file = "/var/lib/tor/onion/mempool-frontend/hostname"; prefix = "http://"; } + { label = "Local Network"; file = "/var/lib/secrets/internal-ip"; prefix = "http://"; suffix = ":60847"; } + ]; } ] # ── Communication ────────────────────────────────────────── ++ [ @@ -205,6 +209,37 @@ LAUNCHER in { config = { + # ── Save internal IP for hub credentials ──────────────────── + systemd.services.save-internal-ip = { + description = "Save internal IP address for hub credentials"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + path = [ pkgs.iproute2 pkgs.coreutils pkgs.hostname ]; + script = '' + mkdir -p /var/lib/secrets + IP=$(hostname -I | awk '{print $1}') + if [ -n "$IP" ]; then + echo "$IP" > /var/lib/secrets/internal-ip + chmod 644 /var/lib/secrets/internal-ip + fi + ''; + }; + + # ── Refresh IP periodically (in case DHCP changes it) ────── + systemd.timers.save-internal-ip = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "30s"; + OnUnitActiveSec = "15min"; + Unit = "save-internal-ip.service"; + }; + }; + # ── Web server as a systemd service ──────────────────────── systemd.services.sovran-hub-web = { description = "Sovran_SystemsOS Hub Web Interface";