frontend uses weblinks

This commit is contained in:
2026-04-02 15:37:07 -05:00
parent ae1f39f0c8
commit b6dfbc4a56
3 changed files with 64 additions and 6 deletions

View File

@@ -85,6 +85,14 @@ function escHtml(str) {
.replace(/'/g, "'"); .replace(/'/g, "'");
} }
function linkify(str) {
const escaped = escHtml(str);
return escaped.replace(
/(https?:\/\/[^\s<]+)/g,
'<a href="$1" target="_blank" rel="noopener noreferrer" class="creds-link">$1</a>'
);
}
// ── Fetch wrappers ──────────────────────────────────────────────── // ── Fetch wrappers ────────────────────────────────────────────────
async function apiFetch(path, options = {}) { async function apiFetch(path, options = {}) {
@@ -269,7 +277,7 @@ async function loadNetwork() {
if ($internalIp) $internalIp.textContent = data.internal_ip || "—"; if ($internalIp) $internalIp.textContent = data.internal_ip || "—";
if ($externalIp) $externalIp.textContent = data.external_ip || "—"; if ($externalIp) $externalIp.textContent = data.external_ip || "—";
} catch (_) { } catch (_) {
if ($internalIp) $internalIp.textContent = "<EFBFBD><EFBFBD>"; if ($internalIp) $internalIp.textContent = "";
if ($externalIp) $externalIp.textContent = "—"; if ($externalIp) $externalIp.textContent = "—";
} }
} }
@@ -310,11 +318,12 @@ async function openCredsModal(unit, name) {
let html = ""; let html = "";
for (const cred of data.credentials) { for (const cred of data.credentials) {
const id = "cred-" + Math.random().toString(36).substring(2, 8); const id = "cred-" + Math.random().toString(36).substring(2, 8);
const displayValue = linkify(cred.value);
html += ` html += `
<div class="creds-row"> <div class="creds-row">
<div class="creds-label">${escHtml(cred.label)}</div> <div class="creds-label">${escHtml(cred.label)}</div>
<div class="creds-value-wrap"> <div class="creds-value-wrap">
<div class="creds-value" id="${id}">${escHtml(cred.value)}</div> <div class="creds-value" id="${id}">${displayValue}</div>
<button class="creds-copy-btn" data-target="${id}">Copy</button> <button class="creds-copy-btn" data-target="${id}">Copy</button>
</div> </div>
</div> </div>

View File

@@ -780,7 +780,7 @@ button.btn-reboot:hover:not(:disabled) {
justify-content: center; justify-content: center;
} }
.service-tile { .service-tile {
width: 160px; : width: 160px;
min-height: 200px; min-height: 200px;
} }
.reboot-card { .reboot-card {
@@ -790,4 +790,18 @@ button.btn-reboot:hover:not(:disabled) {
.creds-dialog { .creds-dialog {
margin: 0 12px; margin: 0 12px;
} }
}
/* ── Credential links ─────────────────────────────────────── */
.creds-link {
color: #58a6ff;
text-decoration: none;
word-break: break-all;
}
.creds-link:hover {
text-decoration: underline;
color: #79c0ff;
}
}

View File

@@ -35,14 +35,18 @@ let
]; } ]; }
{ name = "LND"; unit = "lnd.service"; type = "system"; icon = "lnd"; enabled = cfg.services.bitcoin; category = "bitcoin-apps"; credentials = []; } { 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 = [ { 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"; } { 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 = [ { 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 = "URL"; file = "/var/lib/domains/btcpayserver"; prefix = "https://"; }
{ label = "Note"; value = "Create your admin account on first visit"; } { 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 ────────────────────────────────────────── # ── Communication ──────────────────────────────────────────
++ [ ++ [
@@ -205,6 +209,37 @@ LAUNCHER
in in
{ {
config = { 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 ──────────────────────── # ── Web server as a systemd service ────────────────────────
systemd.services.sovran-hub-web = { systemd.services.sovran-hub-web = {
description = "Sovran_SystemsOS Hub Web Interface"; description = "Sovran_SystemsOS Hub Web Interface";