- Updated BUILD-GUIDE.md to clarify instructions for building the Archipelago Auto-Installer ISO, emphasizing the recommended method of building directly on the target server. - Added auto-installation of missing dependencies (xorriso, podman) when running the build script with sudo. - Enhanced the build-auto-installer-iso.sh script to capture container images from the live server, ensuring the ISO includes the same set of applications as the dev server. - Revised deployment documentation to stress the importance of building the Rust backend on the Linux dev server and included new instructions for capturing system-level changes for ISO builds. - Improved UI components and added new bundled applications (BTCPay Server, Mempool Explorer, Nostr Relay, Strfry Relay, Tailscale) to enhance user experience.
597 lines
28 KiB
HTML
597 lines
28 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>LND - Archipelago</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<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;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.bg-perspective-container {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: -10;
|
||
perspective: 1000px;
|
||
perspective-origin: 50% 50%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.bg-layer {
|
||
position: absolute;
|
||
inset: 0;
|
||
background-image: url('/assets/img/bg-web5.jpg');
|
||
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;
|
||
}
|
||
|
||
/* Glass card - Archipelago standard with gradient border */
|
||
.glass-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: 1rem;
|
||
overflow-x: hidden;
|
||
overflow-y: visible;
|
||
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;
|
||
}
|
||
|
||
.glass-card > * {
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* Glass button - Archipelago standard (secondary actions) */
|
||
.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;
|
||
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);
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 2rem;
|
||
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;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
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;
|
||
}
|
||
}
|
||
|
||
.animate-ping {
|
||
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||
}
|
||
|
||
.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; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="bg-perspective-container">
|
||
<div class="bg-layer"></div>
|
||
</div>
|
||
<div class="overlay"></div>
|
||
|
||
<div class="container">
|
||
<!-- Header - Glass card with logo and Settings button top-right -->
|
||
<div class="glass-card p-6 mb-6">
|
||
<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
|
||
src="/assets/img/app-icons/lnd.svg"
|
||
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>
|
||
</div>
|
||
</div>
|
||
<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"
|
||
>
|
||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 2.31.903 2.31.903a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.903 2.31-.903 2.31a1.724 1.724 0 002.572 1.065c.426 1.756 2.924 1.756 3.35 0a1.724 1.724 0 002.573-1.066c1.543.94 2.31-.903 2.31-.903a1.724 1.724 0 001.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.903-2.31.903-2.31a1.724 1.724 0 00-2.572-1.065z" />
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||
</svg>
|
||
Settings
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Summary strip -->
|
||
<div class="glass-card p-6 mb-6">
|
||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
<div class="info-card flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<div class="relative">
|
||
<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>
|
||
</div>
|
||
<div>
|
||
<p class="text-sm font-medium text-white">Node Status</p>
|
||
<p class="text-xs text-white/60" id="summaryNodeStatus">—</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="info-card flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-2xl text-orange-500 font-bold">⚡</span>
|
||
<div>
|
||
<p class="text-sm font-medium text-white">Channels</p>
|
||
<p class="text-xs text-orange-500 font-medium" id="channelCount">0</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="info-card flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<div class="relative">
|
||
<div class="w-3 h-3 rounded-full bg-green-400" id="restDot"></div>
|
||
</div>
|
||
<div>
|
||
<p class="text-sm font-medium text-white">REST API</p>
|
||
<p class="text-xs text-white/60" id="summaryRestStatus">—</p>
|
||
</div>
|
||
</div>
|
||
<button onclick="openSettings(); setSettingsTab('rest');" class="px-3 py-1.5 glass-button rounded text-xs font-medium">Settings</button>
|
||
</div>
|
||
<div class="info-card flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<div class="relative">
|
||
<div class="w-3 h-3 rounded-full bg-green-400" id="grpcDot"></div>
|
||
</div>
|
||
<div>
|
||
<p class="text-sm font-medium text-white">gRPC</p>
|
||
<p class="text-xs text-white/60" id="summaryGrpcStatus">—</p>
|
||
</div>
|
||
</div>
|
||
<button onclick="openSettings(); setSettingsTab('logs');" class="px-3 py-1.5 glass-button rounded text-xs font-medium">Logs</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
</div>
|
||
</div>
|
||
<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">
|
||
<span class="text-white/80 text-sm">Network</span>
|
||
<span class="text-white/60 text-sm" id="modalNetwork">—</span>
|
||
</div>
|
||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||
<span class="text-white/80 text-sm">Version</span>
|
||
<span class="text-white/60 text-sm" id="modalVersion">—</span>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
<!-- 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>
|
||
</div>
|
||
</div>
|
||
<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">
|
||
<span class="text-white/80 text-sm">API Status</span>
|
||
<span class="text-green-400 text-sm font-medium" id="modalRestStatus">—</span>
|
||
</div>
|
||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||
<span class="text-white/80 text-sm">API Version</span>
|
||
<span class="text-white/60 text-sm" id="modalRestVersion">v1</span>
|
||
</div>
|
||
</div>
|
||
<button class="mt-4 w-full info-card-button text-sm font-medium py-3 rounded-lg" onclick="copyRESTInfo()">Copy REST Info</button>
|
||
</div>
|
||
<!-- 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>
|
||
</div>
|
||
<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">gRPC Host</span>
|
||
<span class="text-white/60 text-sm font-mono" id="modalGrpcHost">—</span>
|
||
</div>
|
||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||
<span class="text-white/80 text-sm">gRPC Status</span>
|
||
<span class="text-green-400 text-sm font-medium" id="modalGrpcStatus">—</span>
|
||
</div>
|
||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||
<span class="text-white/80 text-sm">P2P Port</span>
|
||
<span class="text-white/60 text-sm font-mono">9735</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
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);
|
||
});
|
||
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!'));
|
||
}
|
||
|
||
function openSettings() {
|
||
document.getElementById('settingsModal').classList.remove('hidden');
|
||
document.getElementById('settingsModal').classList.add('flex');
|
||
}
|
||
|
||
function closeSettings() {
|
||
document.getElementById('settingsModal').classList.add('hidden');
|
||
document.getElementById('settingsModal').classList.remove('flex');
|
||
}
|
||
|
||
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' : '—');
|
||
}
|
||
|
||
function setText(id, text) {
|
||
const el = document.getElementById(id);
|
||
if (el) el.textContent = text;
|
||
}
|
||
|
||
async function loadLogs() {
|
||
const logsContent = document.getElementById('logsContent');
|
||
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.';
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
applyLiveData(data);
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
fetchLiveData();
|
||
});
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') closeSettings();
|
||
});
|
||
|
||
document.getElementById('settingsModal').addEventListener('click', (e) => {
|
||
if (e.target.id === 'settingsModal') closeSettings();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|