2026-01-27 23:57:29 +00:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-01-28 00:12:11 +00:00
< title > LND - Archipelago< / title >
2026-03-16 15:34:04 +00:00
< link rel = "stylesheet" href = "tailwind.css" >
2026-01-27 23:57:29 +00:00
< style >
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', sans-serif;
min-height: 100vh;
color: white;
2026-01-28 00:12:11 +00:00
overflow-x: hidden;
2026-01-27 23:57:29 +00:00
}
2026-01-28 00:12:11 +00:00
.bg-perspective-container {
position: fixed;
inset: 0;
z-index: -10;
perspective: 1000px;
perspective-origin: 50% 50%;
overflow: hidden;
}
.bg-layer {
position: absolute;
inset: 0;
2026-03-16 15:34:04 +00:00
background-image: url('assets/img/bg-intro.jpg');
2026-01-28 00:12:11 +00:00
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transition: all 0.45s cubic-bezier(0.68, -0.55, 0.265, 1.55);
transform-style: preserve-3d;
opacity: 1;
transform: translateZ(0) scale(1);
}
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.8);
z-index: -5;
pointer-events: none;
2026-01-27 23:57:29 +00:00
}
2026-02-03 21:49:30 +00:00
/* Glass card - Archipelago standard with gradient border */
2026-01-27 23:57:29 +00:00
.glass-card {
2026-02-03 21:49:30 +00:00
position: relative;
background: rgba(0, 0, 0, 0.60);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.22);
2026-01-27 23:57:29 +00:00
border-radius: 1rem;
2026-01-28 00:12:11 +00:00
overflow-x: hidden;
overflow-y: visible;
2026-02-03 21:49:30 +00:00
border: none;
}
.glass-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
z-index: 1;
2026-01-27 23:57:29 +00:00
}
2026-02-03 21:49:30 +00:00
.glass-card > * {
position: relative;
z-index: 2;
}
/* Glass button - Archipelago standard (secondary actions) */
2026-01-27 23:57:29 +00:00
.glass-button {
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
border: 1px solid rgba(255, 255, 255, 0.18);
color: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
}
.glass-button:hover {
color: white;
2026-02-03 21:49:30 +00:00
background-color: rgba(0, 0, 0, 0.7);
}
/* Info card - Archipelago standard (display only, no hover) */
.info-card {
position: relative;
background: rgba(0, 0, 0, 0.60);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.22);
border-radius: 16px;
padding: 12px;
border: none;
}
.info-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
/* Interactive button - Same as info-card but with hover effects */
.info-card-button {
position: relative;
background: rgba(0, 0, 0, 0.60);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.22);
border-radius: 16px;
padding: 12px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
color: rgba(255, 255, 255, 0.9);
}
.info-card-button::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), transparent);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
transition: all 0.3s ease;
}
.info-card-button:hover {
transform: translateY(-2px);
background: rgba(0, 0, 0, 0.35);
box-shadow:
0 12px 32px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.25);
color: rgba(255, 255, 255, 1);
}
.info-card-button:hover::before {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent);
}
.info-card-button:active {
transform: translateY(1px);
2026-01-27 23:57:29 +00:00
}
2026-01-28 00:12:11 +00:00
.container {
max-width: 1400px;
margin: 0 auto;
2026-01-27 23:57:29 +00:00
padding: 2rem;
2026-01-28 00:12:11 +00:00
padding-bottom: 4rem;
}
/* Logo gradient border */
.logo-gradient-border {
position: relative;
border-radius: 16px;
padding: 3px;
background: linear-gradient(135deg, rgba(139, 92, 246, 0.8) 0%, rgba(0, 0, 0, 0.8) 100%);
box-shadow: 0 8px 24px rgba(139, 92, 246, 0.5);
display: inline-block;
}
.logo-gradient-border::after {
content: '';
position: absolute;
inset: 3px;
border-radius: 13px;
2026-01-28 00:47:00 +00:00
background: rgba(0, 0, 0, 0.4);
2026-01-28 00:12:11 +00:00
z-index: 0;
}
.logo-gradient-border img,
.logo-gradient-border svg {
border-radius: 13px;
display: block;
position: relative;
z-index: 1;
width: 64px;
height: 64px;
}
@keyframes ping {
75%, 100% {
transform: scale(2);
opacity: 0;
}
2026-01-27 23:57:29 +00:00
}
2026-01-28 00:12:11 +00:00
.animate-ping {
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
2026-01-27 23:57:29 +00:00
}
2026-02-14 16:44:20 +00:00
.modal-tab { transition: all 0.2s ease; }
.modal-tab.active { background: rgba(255,255,255,0.2); color: white; }
.modal-tab:not(.active) { color: rgba(255,255,255,0.6); }
.modal-tab:not(.active):hover { color: rgba(255,255,255,0.9); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
2026-01-27 23:57:29 +00:00
< / style >
< / head >
< body >
2026-01-28 00:12:11 +00:00
< div class = "bg-perspective-container" >
< div class = "bg-layer" > < / div >
< / div >
< div class = "overlay" > < / div >
2026-01-27 23:57:29 +00:00
< div class = "container" >
2026-02-14 16:44:20 +00:00
<!-- Header - Glass card with logo and Settings button top - right -->
2026-01-28 00:12:11 +00:00
< div class = "glass-card p-6 mb-6" >
2026-02-14 16:44:20 +00:00
< div class = "flex items-start justify-between gap-4" >
< div class = "flex items-start gap-4 flex-1 min-w-0" >
< div class = "flex-shrink-0" >
< div class = "logo-gradient-border" >
< img
2026-03-16 15:34:04 +00:00
src="assets/img/app-icons/lnd.svg"
2026-02-14 16:44:20 +00:00
alt="LND"
class="w-16 h-16"
style="object-fit: contain; padding: 8px;"
/>
< / div >
< / div >
< div class = "flex-1 min-w-0" >
< h1 class = "text-3xl font-bold text-white mb-2" > LND< / h1 >
< p class = "text-white/70" > Lightning Network Daemon for instant Bitcoin payments< / p >
< p class = "text-sm text-white/60 mt-2" id = "headerNetwork" > —< / p >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< button
onclick="openSettings()"
class="flex-shrink-0 flex items-center gap-2 px-4 py-2.5 glass-button rounded-lg text-sm font-medium"
>
2026-03-16 15:34:04 +00:00
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" stroke-width = "2" viewBox = "0 0 24 24" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" / >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M15 12a3 3 0 11-6 0 3 3 0 016 0z" / >
2026-02-14 16:44:20 +00:00
< / svg >
Settings
< / button >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
<!-- Summary strip -->
2026-01-28 00:12:11 +00:00
< div class = "glass-card p-6 mb-6" >
< div class = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4" >
2026-02-03 21:49:30 +00:00
< div class = "info-card flex items-center justify-between" >
2026-01-28 00:12:11 +00:00
< div class = "flex items-center gap-3" >
< div class = "relative" >
2026-02-14 16:44:20 +00:00
< div class = "w-3 h-3 rounded-full bg-green-400" id = "statusDot" > < / div >
< div class = "absolute inset-0 w-3 h-3 rounded-full bg-green-400 animate-ping opacity-75" id = "statusPing" style = "display:none" > < / div >
2026-01-27 23:57:29 +00:00
< / div >
2026-01-28 00:12:11 +00:00
< div >
< p class = "text-sm font-medium text-white" > Node Status< / p >
2026-02-14 16:44:20 +00:00
< p class = "text-xs text-white/60" id = "summaryNodeStatus" > —< / p >
2026-01-27 23:57:29 +00:00
< / div >
2026-01-28 00:12:11 +00:00
< / div >
< / div >
2026-02-03 21:49:30 +00:00
< div class = "info-card flex items-center justify-between" >
2026-01-28 00:12:11 +00:00
< div class = "flex items-center gap-3" >
2026-02-14 16:44:20 +00:00
< span class = "text-2xl text-orange-500 font-bold" > ⚡ < / span >
2026-01-28 00:12:11 +00:00
< div >
< p class = "text-sm font-medium text-white" > Channels< / p >
< p class = "text-xs text-orange-500 font-medium" id = "channelCount" > 0< / p >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
< / div >
2026-02-03 21:49:30 +00:00
< div class = "info-card flex items-center justify-between" >
2026-01-28 00:12:11 +00:00
< div class = "flex items-center gap-3" >
< div class = "relative" >
2026-02-14 16:44:20 +00:00
< div class = "w-3 h-3 rounded-full bg-green-400" id = "restDot" > < / div >
2026-01-28 00:12:11 +00:00
< / div >
< div >
< p class = "text-sm font-medium text-white" > REST API< / p >
2026-02-14 16:44:20 +00:00
< p class = "text-xs text-white/60" id = "summaryRestStatus" > —< / p >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< button onclick = "openSettings(); setSettingsTab('rest');" class = "px-3 py-1.5 glass-button rounded text-xs font-medium" > Settings< / button >
2026-01-28 00:12:11 +00:00
< / div >
2026-02-03 21:49:30 +00:00
< div class = "info-card flex items-center justify-between" >
2026-01-28 00:12:11 +00:00
< div class = "flex items-center gap-3" >
< div class = "relative" >
2026-02-14 16:44:20 +00:00
< div class = "w-3 h-3 rounded-full bg-green-400" id = "grpcDot" > < / div >
2026-01-28 00:12:11 +00:00
< / div >
< div >
< p class = "text-sm font-medium text-white" > gRPC< / p >
2026-02-14 16:44:20 +00:00
< p class = "text-xs text-white/60" id = "summaryGrpcStatus" > —< / p >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< button onclick = "openSettings(); setSettingsTab('logs');" class = "px-3 py-1.5 glass-button rounded text-xs font-medium" > Logs< / button >
2026-01-28 00:12:11 +00:00
< / div >
< / div >
< / div >
2026-02-14 16:44:20 +00:00
<!-- Main content: placeholder for wallet (balance, Receive, Send, recent activity) -->
< div class = "glass-card p-6 mb-8" >
< h2 class = "text-xl font-semibold text-white mb-4" > Wallet< / h2 >
< p class = "text-white/60 text-sm mb-4" > Balance, Receive, and Send will appear here when connected to your node.< / p >
< div class = "grid grid-cols-1 md:grid-cols-3 gap-4 mb-6" >
< div class = "p-4 bg-white/5 rounded-lg" >
< p class = "text-xs text-white/50 uppercase tracking-wide mb-1" > Spendable< / p >
< p class = "text-lg font-semibold text-white" id = "balanceSpendable" > —< / p >
< / div >
< div class = "p-4 bg-white/5 rounded-lg" >
< p class = "text-xs text-white/50 uppercase tracking-wide mb-1" > Lightning< / p >
< p class = "text-lg font-semibold text-white" id = "balanceLightning" > —< / p >
2026-01-28 00:12:11 +00:00
< / div >
2026-02-14 16:44:20 +00:00
< div class = "p-4 bg-white/5 rounded-lg" >
< p class = "text-xs text-white/50 uppercase tracking-wide mb-1" > Total< / p >
< p class = "text-lg font-semibold text-white" id = "balanceTotal" > —< / p >
< / div >
< / div >
< div class = "flex gap-4" >
< button class = "px-6 py-3 glass-button rounded-lg font-medium text-white/90 hover:text-white transition-colors" disabled > Receive< / button >
< button class = "px-6 py-3 glass-button rounded-lg font-medium text-white/90 hover:text-white transition-colors" disabled > Send< / button >
< / div >
< p class = "text-white/50 text-xs mt-4" > Recent activity will be listed here.< / p >
< / div >
2026-03-16 15:34:04 +00:00
<!-- Connect Your Wallet -->
< div class = "glass-card p-6 mb-8" >
< h2 class = "text-xl font-semibold text-white mb-1" > Connect Your Wallet< / h2 >
< p class = "text-white/70 text-sm mb-4" id = "connSubtitle" > Use a wallet like Zeus, Zap, or BlueWallet to connect remotely.< / p >
<!-- Mode selector -->
< div class = "mb-4" >
< select id = "connMode" onchange = "updateConnInfo()" class = "w-full px-4 py-3 bg-white/5 border border-white/10 rounded-lg text-white text-sm font-medium appearance-none cursor-pointer focus:outline-none focus:border-white/25" style = "background-image: url('data:image/svg+xml;utf8,<svg fill=\" white \ " viewBox = \"0 0 24 24 \ " xmlns = \"http://www.w3.org/2000/svg\" > < path d = \"M7 10l5 5 5-5z \ " / > < / svg > '); background-repeat: no-repeat; background-position: right 12px center; background-size: 20px;">
< option value = "rest-tor" > REST (Tor)< / option >
< option value = "rest-local" > REST (Local Network)< / option >
< option value = "grpc-tor" > gRPC (Tor)< / option >
< option value = "grpc-local" > gRPC (Local Network)< / option >
< / select >
< / div >
<!-- Connection display -->
< div class = "flex flex-col sm:flex-row gap-6" id = "connDisplay" >
< div class = "flex-shrink-0 w-48 h-48 bg-white rounded-xl flex items-center justify-center p-2" id = "lndQrBox" >
< div class = "text-gray-400 text-xs text-center" > Loading...< / div >
< / div >
< div class = "flex-1 space-y-3" >
< div >
< div class = "text-[11px] font-semibold uppercase tracking-wide text-white/45 mb-1" > Host< / div >
< div class = "flex items-center bg-white/5 border border-white/10 rounded-lg overflow-hidden" >
< span class = "flex-1 px-3 py-2.5 font-mono text-sm text-white/90 truncate" id = "connHost" > —< / span >
< button onclick = "copyEl('connHost', this)" class = "px-3 py-2.5 border-l border-white/10 text-white/40 hover:text-white/80 hover:bg-white/5 transition-colors" >
< svg width = "16" height = "16" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < rect x = "9" y = "9" width = "13" height = "13" rx = "2" stroke-width = "2" / > < path d = "M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" stroke-width = "2" / > < / svg >
< / button >
< / div >
< / div >
< div class = "flex gap-3" >
< div class = "flex-1" >
< div class = "text-[11px] font-semibold uppercase tracking-wide text-white/45 mb-1" > Port< / div >
< div class = "flex items-center bg-white/5 border border-white/10 rounded-lg overflow-hidden" >
< span class = "flex-1 px-3 py-2.5 font-mono text-sm text-white/90" id = "connPort" > —< / span >
< button onclick = "copyEl('connPort', this)" class = "px-3 py-2.5 border-l border-white/10 text-white/40 hover:text-white/80 hover:bg-white/5 transition-colors" >
< svg width = "16" height = "16" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < rect x = "9" y = "9" width = "13" height = "13" rx = "2" stroke-width = "2" / > < path d = "M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" stroke-width = "2" / > < / svg >
< / button >
< / div >
< / div >
< div class = "flex-1" >
< div class = "text-[11px] font-semibold uppercase tracking-wide text-white/45 mb-1" > Protocol< / div >
< div class = "flex items-center bg-white/5 border border-white/10 rounded-lg overflow-hidden" >
< span class = "flex-1 px-3 py-2.5 font-mono text-sm text-white/90" id = "connProto" > —< / span >
< / div >
< / div >
< / div >
< button onclick = "copyLndconnectUri()" class = "w-full mt-2 px-4 py-2.5 glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors" id = "copyUriBtn" > Copy lndconnect URI< / button >
< / div >
< / div >
< div class = "mt-4 pt-4 border-t border-white/8 text-sm text-white/50 leading-relaxed" >
Scan the QR code with < strong class = "text-white/80" > Zeus< / strong > , < strong class = "text-white/80" > Zap< / strong > , or < strong class = "text-white/80" > BlueWallet< / strong > to connect. Tor mode recommended for remote access.
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< / div >
2026-01-28 00:12:11 +00:00
2026-02-14 16:44:20 +00:00
<!-- Tabbed Settings Modal -->
< div class = "modal hidden fixed inset-0 bg-black/80 backdrop-blur-sm z-50 items-center justify-center p-4" id = "settingsModal" >
< div class = "glass-card p-6 max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col" >
< div class = "flex justify-between items-center mb-4 flex-shrink-0" >
< h2 class = "text-2xl font-bold text-white" > Settings< / h2 >
< button onclick = "closeSettings()" class = "glass-button px-3 py-2 rounded-lg text-xl font-medium" > × < / button >
< / div >
< div class = "flex gap-2 mb-4 flex-shrink-0 glass-card p-2 rounded-lg" >
< button class = "modal-tab flex-1 px-4 py-2 rounded-lg text-sm font-medium active" data-tab = "node" > Node Status< / button >
< button class = "modal-tab flex-1 px-4 py-2 rounded-lg text-sm font-medium" data-tab = "rest" > REST API< / button >
< button class = "modal-tab flex-1 px-4 py-2 rounded-lg text-sm font-medium" data-tab = "grpc" > gRPC< / button >
< button class = "modal-tab flex-1 px-4 py-2 rounded-lg text-sm font-medium" data-tab = "logs" > Logs< / button >
< / div >
< div class = "overflow-y-auto flex-1 min-h-0 space-y-4" >
<!-- Node Status tab -->
< div id = "panel-node" class = "tab-panel active" >
< div class = "flex items-start gap-4 mb-4" >
< div class = "flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center" >
< svg class = "w-6 h-6 text-white/80" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M13 10V3L4 14h7v7l9-11h-7z" / > < / svg >
< / div >
< div class = "flex-1" >
< h3 class = "text-lg font-semibold text-white mb-1" > Node Status< / h3 >
< p class = "text-white/70 text-sm" > Lightning node information< / p >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< div class = "space-y-3" >
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
< span class = "text-white/80 text-sm" > Node Status< / span >
< span class = "text-green-400 text-sm font-medium" id = "modalNodeStatus" > —< / span >
< / div >
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
2026-01-28 00:12:11 +00:00
< span class = "text-white/80 text-sm" > Network< / span >
2026-02-14 16:44:20 +00:00
< span class = "text-white/60 text-sm" id = "modalNetwork" > —< / span >
2026-01-27 23:57:29 +00:00
< / div >
2026-02-14 16:44:20 +00:00
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
2026-01-28 00:12:11 +00:00
< span class = "text-white/80 text-sm" > Version< / span >
2026-02-14 16:44:20 +00:00
< span class = "text-white/60 text-sm" id = "modalVersion" > —< / span >
2026-01-28 00:12:11 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< div class = "mt-4 space-y-3" >
< div class = "p-3 bg-white/5 rounded-lg" >
< div class = "font-semibold text-white mb-1" > Network Mode< / div >
< div class = "text-white/70 text-sm" id = "modalNetworkMode" > —< / div >
< / div >
< div class = "p-3 bg-white/5 rounded-lg" >
< div class = "font-semibold text-white mb-1" > Bitcoin Backend< / div >
< div class = "text-white/70 text-sm" id = "modalBitcoinBackend" > —< / div >
< / div >
2026-01-28 00:12:11 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
<!-- REST API tab -->
< div id = "panel-rest" class = "tab-panel" >
< div class = "flex items-start gap-4 mb-4" >
< div class = "flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center" >
< svg class = "w-6 h-6 text-white/80" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01" / > < / svg >
< / div >
< div class = "flex-1" >
< h3 class = "text-lg font-semibold text-white mb-1" > REST API< / h3 >
< p class = "text-white/70 text-sm" > HTTP REST API access< / p >
2026-01-28 00:12:11 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< div class = "space-y-3" >
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
< span class = "text-white/80 text-sm" > REST Endpoint< / span >
< span class = "text-white/60 text-sm font-mono" id = "modalRestEndpoint" > —< / span >
< / div >
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
2026-01-28 00:12:11 +00:00
< span class = "text-white/80 text-sm" > API Status< / span >
2026-02-14 16:44:20 +00:00
< span class = "text-green-400 text-sm font-medium" id = "modalRestStatus" > —< / span >
2026-01-28 00:12:11 +00:00
< / div >
2026-02-14 16:44:20 +00:00
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
2026-01-28 00:12:11 +00:00
< span class = "text-white/80 text-sm" > API Version< / span >
2026-02-14 16:44:20 +00:00
< span class = "text-white/60 text-sm" id = "modalRestVersion" > v1< / span >
2026-01-28 00:12:11 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
< button class = "mt-4 w-full info-card-button text-sm font-medium py-3 rounded-lg" onclick = "copyRESTInfo()" > Copy REST Info< / button >
2026-01-28 00:12:11 +00:00
< / div >
2026-02-14 16:44:20 +00:00
<!-- gRPC tab -->
< div id = "panel-grpc" class = "tab-panel" >
< div class = "flex items-start gap-4 mb-4" >
< div class = "flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center" >
< svg class = "w-6 h-6 text-white/80" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01" / > < / svg >
< / div >
< div class = "flex-1" >
< h3 class = "text-lg font-semibold text-white mb-1" > gRPC Connection< / h3 >
< p class = "text-white/70 text-sm" > High-performance gRPC API< / p >
< / div >
2026-01-28 00:12:11 +00:00
< / div >
2026-02-14 16:44:20 +00:00
< div class = "space-y-3" >
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
2026-01-28 00:12:11 +00:00
< span class = "text-white/80 text-sm" > gRPC Host< / span >
2026-02-14 16:44:20 +00:00
< span class = "text-white/60 text-sm font-mono" id = "modalGrpcHost" > —< / span >
2026-01-28 00:12:11 +00:00
< / div >
2026-02-14 16:44:20 +00:00
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
2026-01-28 00:12:11 +00:00
< span class = "text-white/80 text-sm" > gRPC Status< / span >
2026-02-14 16:44:20 +00:00
< span class = "text-green-400 text-sm font-medium" id = "modalGrpcStatus" > —< / span >
2026-01-28 00:12:11 +00:00
< / div >
2026-02-14 16:44:20 +00:00
< div class = "flex items-center justify-between p-3 bg-white/5 rounded-lg" >
2026-01-28 00:12:11 +00:00
< span class = "text-white/80 text-sm" > P2P Port< / span >
2026-02-14 16:44:20 +00:00
< span class = "text-white/60 text-sm font-mono" > 9735< / span >
2026-01-28 00:12:11 +00:00
< / div >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
2026-02-14 16:44:20 +00:00
<!-- Logs tab -->
< div id = "panel-logs" class = "tab-panel" >
< div class = "flex justify-between items-center mb-3" >
< h3 class = "text-lg font-semibold text-white" > Node Logs< / h3 >
< button onclick = "loadLogs()" class = "px-3 py-1.5 glass-button rounded text-xs font-medium" > Refresh< / button >
< / div >
< div class = "bg-black/40 rounded-lg p-4 font-mono text-xs text-white/80 whitespace-pre-wrap break-all min-h-[200px]" id = "logsContent" > Loading logs...< / div >
2026-01-28 00:12:11 +00:00
< / div >
2026-01-27 23:57:29 +00:00
< / div >
< / div >
< / div >
2026-03-16 15:34:04 +00:00
< script src = "qrcode.js" > < / script >
2026-01-27 23:57:29 +00:00
< script >
2026-02-14 16:44:20 +00:00
const REST_PORT = 8080;
const GRPC_PORT = 10009;
const P2P_PORT = 9735;
const host = window.location.hostname;
function getBackendUrl() {
const params = new URLSearchParams(window.location.search);
return params.get('backend') || '';
}
function setSettingsTab(tabId) {
document.querySelectorAll('.modal-tab').forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-tab') === tabId);
2026-01-27 23:57:29 +00:00
});
2026-02-14 16:44:20 +00:00
document.querySelectorAll('.tab-panel').forEach(panel => {
panel.classList.toggle('active', panel.id === 'panel-' + tabId);
});
if (tabId === 'logs') loadLogs();
}
document.querySelectorAll('.modal-tab').forEach(btn => {
btn.addEventListener('click', () => setSettingsTab(btn.getAttribute('data-tab')));
});
function copyRESTInfo() {
const endpoint = host + ':' + REST_PORT;
const info = 'REST API: http://' + endpoint + '\nAPI Version: v1';
navigator.clipboard.writeText(info).then(() => alert('REST info copied to clipboard!'));
2026-01-27 23:57:29 +00:00
}
function openSettings() {
2026-01-28 00:12:11 +00:00
document.getElementById('settingsModal').classList.remove('hidden');
document.getElementById('settingsModal').classList.add('flex');
2026-01-27 23:57:29 +00:00
}
function closeSettings() {
2026-01-28 00:12:11 +00:00
document.getElementById('settingsModal').classList.add('hidden');
document.getElementById('settingsModal').classList.remove('flex');
2026-01-27 23:57:29 +00:00
}
2026-02-14 16:44:20 +00:00
function applyLiveData(data) {
if (data.getinfo) {
const g = data.getinfo;
const status = g.synced_to_chain ? 'Running' : 'Waiting for chain…';
const network = (g.chains & & g.chains[0]) ? g.chains[0].network || '—' : '—';
const version = g.version || '—';
setText('headerNetwork', 'Network: ' + network);
setText('summaryNodeStatus', status);
setText('modalNodeStatus', status);
setText('modalNetwork', network);
setText('modalVersion', version);
setText('modalNetworkMode', network);
setText('modalBitcoinBackend', '—');
document.getElementById('statusPing').style.display = g.synced_to_chain ? 'block' : 'none';
}
if (data.channelCount !== undefined) {
setText('channelCount', String(data.channelCount));
}
setText('modalRestEndpoint', host + ':' + REST_PORT);
setText('modalRestStatus', data.restReachable ? 'Active' : '—');
setText('summaryRestStatus', data.restReachable ? 'Active' : '—');
setText('modalGrpcHost', host + ':' + GRPC_PORT);
setText('modalGrpcStatus', data.grpcReachable ? 'Connected' : '—');
setText('summaryGrpcStatus', data.grpcReachable ? 'Connected' : '—');
2026-01-27 23:57:29 +00:00
}
2026-02-14 16:44:20 +00:00
function setText(id, text) {
const el = document.getElementById(id);
if (el) el.textContent = text;
2026-01-27 23:57:29 +00:00
}
2026-02-14 16:44:20 +00:00
async function loadLogs() {
2026-01-27 23:57:29 +00:00
const logsContent = document.getElementById('logsContent');
2026-02-14 16:44:20 +00:00
const backendUrl = getBackendUrl();
if (backendUrl) {
logsContent.textContent = 'Loading logs...';
try {
const res = await fetch(backendUrl + '/api/container/logs?app_id=lnd&lines=200');
if (!res.ok) throw new Error(res.statusText);
const json = await res.json();
const lines = json.result || json.logs || (Array.isArray(json) ? json : []);
logsContent.textContent = Array.isArray(lines) ? lines.join('\n') : String(lines);
} catch (e) {
logsContent.textContent = 'Could not load logs: ' + e.message;
}
} else {
logsContent.textContent = 'Open this app with ?backend=http://HOST:5678 to load logs from the server.';
2026-01-27 23:57:29 +00:00
}
2026-02-14 16:44:20 +00:00
}
2026-01-27 23:57:29 +00:00
2026-02-14 16:44:20 +00:00
async function fetchLiveData() {
const backendUrl = getBackendUrl();
const data = { channelCount: 0, restReachable: false, grpcReachable: false };
if (backendUrl) {
try {
const getinfoRes = await fetch(backendUrl + '/proxy/lnd/v1/getinfo');
if (getinfoRes.ok) {
data.getinfo = await getinfoRes.json();
data.restReachable = true;
}
} catch (_) {}
try {
const chRes = await fetch(backendUrl + '/proxy/lnd/v1/channels');
if (chRes.ok) {
const ch = await chRes.json();
data.channelCount = (ch.channels & & ch.channels.length) || 0;
}
} catch (_) {}
data.grpcReachable = data.restReachable;
2026-01-27 23:57:29 +00:00
}
2026-02-14 16:44:20 +00:00
applyLiveData(data);
}
document.addEventListener('DOMContentLoaded', () => {
fetchLiveData();
2026-01-27 23:57:29 +00:00
});
2026-02-14 16:44:20 +00:00
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeSettings();
});
document.getElementById('settingsModal').addEventListener('click', (e) => {
if (e.target.id === 'settingsModal') closeSettings();
2026-01-27 23:57:29 +00:00
});
2026-03-16 15:34:04 +00:00
// --- Connect Your Wallet ---
let lndConnInfo = null;
function renderQR(containerId, text) {
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = '';
try {
const qr = qrcode(0, 'L');
qr.addData(text);
qr.make();
container.innerHTML = qr.createImgTag(3, 0);
} catch(e) {
container.innerHTML = '< div style = "color:#999;font-size:11px;text-align:center;padding:2rem" > QR too large for this mode< / div > ';
}
}
function buildLndconnectUri(connHost, connPort, cert, macaroon, isTor) {
let uri = 'lndconnect://' + connHost + ':' + connPort + '?';
if (!isTor & & cert) uri += 'cert=' + cert + '&';
uri += 'macaroon=' + macaroon;
return uri;
}
function updateConnInfo() {
if (!lndConnInfo) return;
const mode = document.getElementById('connMode').value;
const isTor = mode.includes('tor');
const isRest = mode.includes('rest');
const port = isRest ? lndConnInfo.rest_port : lndConnInfo.grpc_port;
const connHost = isTor & & lndConnInfo.tor_onion ? lndConnInfo.tor_onion : host;
const proto = isRest ? 'REST' : 'gRPC';
setText('connHost', connHost);
setText('connPort', String(port));
setText('connProto', proto);
if (isTor & & !lndConnInfo.tor_onion) {
document.getElementById('lndQrBox').innerHTML = '< div style = "color:#999;font-size:12px;text-align:center;padding:2rem" > Tor not configured for LND< / div > ';
document.getElementById('connSubtitle').textContent = 'Tor hidden service not available. Use Local Network mode.';
return;
}
document.getElementById('connSubtitle').textContent = 'Scan QR with Zeus, Zap, or BlueWallet to connect.';
const uri = buildLndconnectUri(connHost, port, lndConnInfo.cert_base64url, lndConnInfo.macaroon_base64url, isTor);
renderQR('lndQrBox', uri);
}
function copyEl(id, btn) {
const text = document.getElementById(id).textContent.trim();
if (!text || text === '—') return;
navigator.clipboard.writeText(text).then(() => {
const orig = btn.innerHTML;
btn.innerHTML = '< svg width = "16" height = "16" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M5 13l4 4L19 7" / > < / svg > ';
btn.style.color = '#4ade80';
setTimeout(() => { btn.innerHTML = orig; btn.style.color = ''; }, 1500);
});
}
function copyLndconnectUri() {
if (!lndConnInfo) return;
const mode = document.getElementById('connMode').value;
const isTor = mode.includes('tor');
const isRest = mode.includes('rest');
const port = isRest ? lndConnInfo.rest_port : lndConnInfo.grpc_port;
const connHost = isTor & & lndConnInfo.tor_onion ? lndConnInfo.tor_onion : host;
const uri = buildLndconnectUri(connHost, port, lndConnInfo.cert_base64url, lndConnInfo.macaroon_base64url, isTor);
const btn = document.getElementById('copyUriBtn');
navigator.clipboard.writeText(uri).then(() => {
const orig = btn.textContent;
btn.textContent = 'Copied!';
btn.style.color = '#4ade80';
setTimeout(() => { btn.textContent = orig; btn.style.color = ''; }, 1500);
});
}
async function fetchConnectInfo() {
try {
const resp = await fetch('http://' + window.location.hostname + '/lnd-connect-info');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
const data = await resp.json();
if (data.cert_base64url) {
lndConnInfo = data;
try { updateConnInfo(); } catch(ue) {
document.getElementById('lndQrBox').innerHTML = '< div style = "color:#f87171;font-size:11px;text-align:center;padding:1rem" > Update error: ' + ue.message + '< / div > ';
return;
}
} else if (data.error) {
throw new Error(data.error);
}
} catch(e) {
document.getElementById('lndQrBox').innerHTML = '< div style = "color:#f87171;font-size:12px;text-align:center;padding:2rem" > ' + e.message + '< / div > ';
}
}
fetchConnectInfo();
2026-01-27 23:57:29 +00:00
< / script >
< / body >
< / html >