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>
|
|
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
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-01-28 00:47:00 +00:00
|
|
|
|
background-image: url('/assets/img/bg-web5.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
|
|
|
|
|
|
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>
|
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"
|
|
|
|
|
|
>
|
|
|
|
|
|
<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>
|
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>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
|
|
<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
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|