257 lines
5.7 KiB
HTML
257 lines
5.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Munin Bitcoin</title>
|
|
<style>
|
|
:root {
|
|
color-scheme: dark;
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
background: #111827;
|
|
color: #f9fafb;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
min-height: 100vh;
|
|
background: #111827;
|
|
}
|
|
|
|
main {
|
|
max-width: 760px;
|
|
margin: 0 auto;
|
|
padding: 48px 24px;
|
|
}
|
|
|
|
h1 {
|
|
margin: 0 0 8px;
|
|
font-size: 32px;
|
|
}
|
|
|
|
.subtitle {
|
|
margin: 0 0 32px;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.card {
|
|
background: #1f2937;
|
|
border: 1px solid #374151;
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 14px 0;
|
|
border-bottom: 1px solid #374151;
|
|
}
|
|
|
|
.row:last-child {
|
|
border-bottom: 0;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.row:first-child {
|
|
padding-top: 0;
|
|
}
|
|
|
|
.label {
|
|
color: #d1d5db;
|
|
}
|
|
|
|
.value {
|
|
text-align: right;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: #6b7280;
|
|
}
|
|
|
|
.status.connected .dot,
|
|
.status.running .dot {
|
|
background: #22c55e;
|
|
}
|
|
|
|
.status.disconnected .dot {
|
|
background: #ef4444;
|
|
}
|
|
|
|
.muted {
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.error {
|
|
color: #fca5a5;
|
|
}
|
|
|
|
button {
|
|
margin-top: 16px;
|
|
padding: 10px 16px;
|
|
border: 0;
|
|
border-radius: 8px;
|
|
background: #374151;
|
|
color: #f9fafb;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
}
|
|
|
|
button:hover {
|
|
background: #4b5563;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<h1>Munin Bitcoin</h1>
|
|
<p class="subtitle">Bitcoin watch-only wallet monitoring and label management</p>
|
|
|
|
<section class="card">
|
|
<div class="row">
|
|
<span class="label">Munin service</span>
|
|
<span id="service-status" class="status">
|
|
<span class="dot"></span>
|
|
<span>Checking...</span>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<span class="label">Fulcrum (Electrum server)</span>
|
|
<span id="fulcrum-status" class="status">
|
|
<span class="dot"></span>
|
|
<span>Checking...</span>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<span class="label">Fulcrum endpoint</span>
|
|
<span id="fulcrum-url" class="value muted">Checking...</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<div class="row">
|
|
<span class="label">Wallet</span>
|
|
<span id="wallet" class="value muted">Not configured</span>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<span class="label">Labels</span>
|
|
<span id="labels" class="value">0</span>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<span class="label">Transactions</span>
|
|
<span id="transactions" class="value">0</span>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<span class="label">Last scan</span>
|
|
<span id="last-scan" class="value muted">Never</span>
|
|
</div>
|
|
|
|
<button type="button" onclick="loadStatus()">Refresh status</button>
|
|
|
|
<p id="error" class="error"></p>
|
|
</section>
|
|
</main>
|
|
|
|
<script>
|
|
function setStatus(element, text, state) {
|
|
element.className = `status ${state}`;
|
|
element.querySelector('span:last-child').textContent = text;
|
|
}
|
|
|
|
async function loadStatus() {
|
|
const errorElement = document.getElementById('error');
|
|
errorElement.textContent = '';
|
|
|
|
try {
|
|
const response = await fetch('/api/status');
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}`);
|
|
}
|
|
|
|
const status = await response.json();
|
|
|
|
setStatus(
|
|
document.getElementById('service-status'),
|
|
status.service === 'running' ? 'Running' : 'Not running',
|
|
status.service === 'running' ? 'running' : 'disconnected',
|
|
);
|
|
|
|
const fulcrum = status.fulcrum || {};
|
|
|
|
if (fulcrum.connected) {
|
|
setStatus(
|
|
document.getElementById('fulcrum-status'),
|
|
'Connected',
|
|
'connected',
|
|
);
|
|
} else if (fulcrum.configured) {
|
|
setStatus(
|
|
document.getElementById('fulcrum-status'),
|
|
'Not connected',
|
|
'disconnected',
|
|
);
|
|
} else {
|
|
setStatus(
|
|
document.getElementById('fulcrum-status'),
|
|
'Not configured',
|
|
'disconnected',
|
|
);
|
|
}
|
|
|
|
document.getElementById('fulcrum-url').textContent =
|
|
fulcrum.url || 'Not configured';
|
|
|
|
document.getElementById('wallet').textContent =
|
|
status.wallet || 'Not configured';
|
|
|
|
document.getElementById('labels').textContent =
|
|
status.labels ?? 0;
|
|
|
|
document.getElementById('transactions').textContent =
|
|
status.transactions ?? 0;
|
|
|
|
document.getElementById('last-scan').textContent =
|
|
status.lastScan || 'Never';
|
|
} catch (error) {
|
|
setStatus(
|
|
document.getElementById('service-status'),
|
|
'Unavailable',
|
|
'disconnected',
|
|
);
|
|
|
|
setStatus(
|
|
document.getElementById('fulcrum-status'),
|
|
'Unavailable',
|
|
'disconnected',
|
|
);
|
|
|
|
document.getElementById('fulcrum-url').textContent = 'Unavailable';
|
|
errorElement.textContent = 'Unable to retrieve service status.';
|
|
}
|
|
}
|
|
|
|
loadStatus();
|
|
setInterval(loadStatus, 10000);
|
|
</script>
|
|
</body>
|
|
</html> |