frontend uses weblinks
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user