230 lines
13 KiB
HTML
230 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>BTCPay Manager — CCSite Fleet</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
<style>
|
|
:root {
|
|
--bg: #080812; --card: #12121f; --accent: #6c63ff; --accent2: #00d4aa;
|
|
--text: #e0e0e0; --muted: #777; --danger: #ff4757; --success: #2ed573; --warning: #ffa502;
|
|
--btc: #f7931a;
|
|
--radius: 12px;
|
|
}
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'SF Mono', monospace; background: var(--bg); color: var(--text); min-height: 100vh; }
|
|
header { background: var(--card); border-bottom: 1px solid rgba(108,99,255,0.2); padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; z-index: 100; }
|
|
header h1 { font-size: 1.2rem; font-weight: 800; background: linear-gradient(135deg, var(--btc), var(--accent2)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
header nav a { color: var(--muted); text-decoration: none; font-size: 0.75rem; margin-left: 1rem; padding: 0.3rem 0.7rem; border-radius: 4px; border: 1px solid rgba(108,99,255,0.2); transition: all 0.2s; }
|
|
header nav a:hover, header nav a.active { color: var(--accent2); border-color: var(--accent2); }
|
|
.container { max-width: 1400px; margin: 0 auto; padding: 1.5rem; }
|
|
|
|
.card { background: var(--card); padding: 1.5rem; border-radius: var(--radius); border: 1px solid rgba(108,99,255,0.1); margin-bottom: 1rem; }
|
|
.card h3 { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 1rem; }
|
|
|
|
.big-number { font-size: 3rem; font-weight: 800; font-family: 'SF Mono', monospace; }
|
|
.label-sm { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 1.5px; color: var(--muted); }
|
|
|
|
.progress-bar { height: 12px; background: rgba(255,255,255,0.05); border-radius: 6px; overflow: hidden; margin: 0.5rem 0; }
|
|
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--btc), #ffd700); border-radius: 6px; transition: width 30s ease; }
|
|
|
|
table { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
|
|
th { text-align: left; padding: 0.7rem 0.5rem; border-bottom: 1px solid rgba(108,99,255,0.1); font-size: 0.6rem; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); }
|
|
td { padding: 0.6rem 0.5rem; border-bottom: 1px solid rgba(108,99,255,0.05); }
|
|
tr:hover { background: rgba(108,99,255,0.03); }
|
|
.badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 3px; font-size: 0.65rem; font-weight: 600; }
|
|
.badge-ok { background: rgba(46,213,115,0.15); color: var(--success); }
|
|
.badge-warn { background: rgba(255,165,2,0.15); color: var(--warning); }
|
|
.badge-err { background: rgba(255,71,87,0.15); color: var(--danger); }
|
|
|
|
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
|
|
|
|
.chart-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
|
|
.chart-box { background: var(--card); padding: 1.5rem; border-radius: var(--radius); border: 1px solid rgba(108,99,255,0.1); }
|
|
.chart-box canvas { max-height: 250px; }
|
|
|
|
.pulse { animation: pulse 2s infinite; } @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
|
|
@media (max-width: 768px) { .chart-row { grid-template-columns: 1fr; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div>
|
|
<h1>₿ BTCPay Server Manager</h1>
|
|
<span style="font-size:0.7rem;color:var(--muted);">10.30.20.140 · <span id="clock">--</span></span>
|
|
</div>
|
|
<nav>
|
|
<a href="/">Dashboard</a>
|
|
<a href="/master">Master Panel</a>
|
|
<a href="/btcpay" class="active">₿ BTCPay</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<div class="container">
|
|
<!-- Sync Status -->
|
|
<div class="card" style="border:1px solid rgba(243,156,18,0.3);">
|
|
<div style="display:flex;gap:2rem;align-items:center;flex-wrap:wrap;">
|
|
<div>
|
|
<div class="label-sm">Sync Progress</div>
|
|
<div class="big-number" style="color:var(--btc);" id="sync-pct">--</div>
|
|
</div>
|
|
<div style="flex:1;min-width:250px;">
|
|
<div class="progress-bar"><div class="progress-fill" id="sync-bar" style="width:0%"></div></div>
|
|
<div style="font-size:0.7rem;color:var(--muted);display:flex;justify-content:space-between;">
|
|
<span id="blocks-display">--</span>
|
|
<span id="headers-display">--</span>
|
|
</div>
|
|
<div style="margin-top:0.5rem;font-size:0.7rem;">
|
|
<span class="badge" id="sync-badge">Syncing</span>
|
|
<span style="color:var(--muted);margin-left:0.5rem;">v<span id="btcpay-version">--</span></span>
|
|
</div>
|
|
</div>
|
|
<div style="text-align:right;">
|
|
<div class="label-sm">Wallets Ready</div>
|
|
<div class="big-number" id="wallets-ready" style="color:var(--warning);">0/10</div>
|
|
<div class="label-sm">Total Invoices: <span id="total-invoices" style="color:var(--btc);">0</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Row -->
|
|
<div class="stat-row" id="stat-row"></div>
|
|
|
|
<!-- Charts -->
|
|
<div class="chart-row">
|
|
<div class="chart-box">
|
|
<h3>📈 Sync Progress (24h)</h3>
|
|
<canvas id="sync-chart" height="200"></canvas>
|
|
</div>
|
|
<div class="chart-box">
|
|
<h3>🏪 Per-Store Invoice Count</h3>
|
|
<canvas id="invoice-chart" height="200"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Store Table -->
|
|
<div class="card">
|
|
<h3>📋 Store Status</h3>
|
|
<table>
|
|
<thead><tr><th>#</th><th>Site</th><th>Store ID</th><th>Wallet</th><th>Invoices</th><th>Webhook</th><th>Status</th></tr></thead>
|
|
<tbody id="store-table"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Webhook Health -->
|
|
<div class="card">
|
|
<h3>🔗 Webhook Status</h3>
|
|
<div id="webhook-status" style="font-size:0.75rem;color:var(--muted);">Loading...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const EM = {1:'⚡',2:'💳',3:'👤',4:'🔑',5:'₿',6:'🎁',7:'⭐',8:'🔥',9:'🎮',10:'🏆'};
|
|
let syncChart, invoiceChart;
|
|
|
|
function initCharts() {
|
|
syncChart = new Chart(document.getElementById('sync-chart').getContext('2d'), {
|
|
type: 'line',
|
|
data: { labels: [], datasets: [{ label: 'Sync %', data: [], borderColor: '#f7931a', backgroundColor: 'rgba(247,147,26,0.1)', fill: true, tension: 0.3, pointRadius: 0 }] },
|
|
options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: false } },
|
|
scales: { x: { ticks: { color: '#777', font: { size: 9 } } }, y: { min: 0, max: 100, ticks: { color: '#777', callback: v => v+'%' } } } }
|
|
});
|
|
invoiceChart = new Chart(document.getElementById('invoice-chart').getContext('2d'), {
|
|
type: 'bar',
|
|
data: { labels: [], datasets: [{ label: 'Invoices', data: [], backgroundColor: '#f7931a', borderRadius: 4 }] },
|
|
options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: false } },
|
|
scales: { x: { ticks: { color: '#777', font: { size: 8 } } }, y: { ticks: { color: '#777' }, min: 0 } } }
|
|
});
|
|
}
|
|
|
|
async function refresh() {
|
|
try {
|
|
const r = await fetch('/api/state');
|
|
const data = await r.json();
|
|
const bp = data.btcpay || {};
|
|
const sites = data.sites || {};
|
|
|
|
// Sync status
|
|
document.getElementById('sync-pct').textContent = (bp.sync_pct||0).toFixed(1) + '%';
|
|
document.getElementById('sync-bar').style.width = (bp.sync_pct||0) + '%';
|
|
document.getElementById('blocks-display').textContent = (bp.blocks||0).toLocaleString() + ' blocks synced';
|
|
document.getElementById('headers-display').textContent = 'Chain height: ' + (bp.headers||'?').toLocaleString();
|
|
document.getElementById('btcpay-version').textContent = bp.version || '?';
|
|
|
|
const badge = document.getElementById('sync-badge');
|
|
if (bp.synced) {
|
|
badge.textContent = '✅ Synced'; badge.className = 'badge badge-ok';
|
|
} else {
|
|
badge.textContent = '⏳ Syncing'; badge.className = 'badge badge-warn';
|
|
}
|
|
|
|
const wallets = bp.stores_with_wallet || 0;
|
|
const total = bp.total_stores || 10;
|
|
const wel = document.getElementById('wallets-ready');
|
|
wel.textContent = wallets + '/' + total;
|
|
wel.style.color = wallets === total ? 'var(--success)' : 'var(--warning)';
|
|
document.getElementById('total-invoices').textContent = bp.total_invoices || 0;
|
|
|
|
// Stats row
|
|
document.getElementById('stat-row').innerHTML = `
|
|
<div class="card"><div class="label-sm">BTCPay Server</div><div class="big-number" style="font-size:1.5rem;color:var(--accent2);">v${bp.version||'?'}</div></div>
|
|
<div class="card"><div class="label-sm">Total Stores</div><div class="big-number" style="font-size:1.5rem;">${total}</div></div>
|
|
<div class="card"><div class="label-sm">Wallets Ready</div><div class="big-number" style="font-size:1.5rem;color:${wallets===total?'var(--success)':'var(--warning)'};">${wallets}/${total}</div></div>
|
|
<div class="card"><div class="label-sm">Total Invoices</div><div class="big-number" style="font-size:1.5rem;color:var(--btc);">${bp.total_invoices||0}</div></div>
|
|
`;
|
|
|
|
// Store table
|
|
const table = document.getElementById('store-table');
|
|
table.innerHTML = '';
|
|
for (let i=1; i<=10; i++) {
|
|
const s = bp.stores?.[i] || {};
|
|
const site = sites[i] || {};
|
|
table.innerHTML += `
|
|
<tr>
|
|
<td style="color:var(--muted);">#${i}</td>
|
|
<td>${EM[i]} ${site.name||'?'}</td>
|
|
<td style="font-family:'SF Mono',monospace;font-size:0.65rem;color:var(--muted);">${s.store_id||'?'}</td>
|
|
<td><span class="badge ${s.has_wallet?'badge-ok':'badge-warn'}">${s.has_wallet?'Ready':'Pending'}</span></td>
|
|
<td>${s.invoice_count||0}</td>
|
|
<td><span class="badge badge-ok">Active</span></td>
|
|
<td><a href="http://${site.ip}:5000" target="_blank" style="color:var(--accent);font-size:0.65rem;">Visit →</a></td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
// Invoice chart
|
|
const labels = [], invCounts = [];
|
|
for (let i=1; i<=10; i++) {
|
|
labels.push(EM[i]);
|
|
invCounts.push(bp.stores?.[i]?.invoice_count || 0);
|
|
}
|
|
invoiceChart.data.labels = labels;
|
|
invoiceChart.data.datasets[0].data = invCounts;
|
|
invoiceChart.update();
|
|
|
|
// Sync chart
|
|
if (data.btcpay_history && data.btcpay_history.length > 1) {
|
|
syncChart.data.labels = data.btcpay_history.map(h => new Date(h.ts*1000).toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'}));
|
|
syncChart.data.datasets[0].data = data.btcpay_history.map(h => h.sync_pct);
|
|
syncChart.update();
|
|
}
|
|
|
|
// Webhook summary
|
|
document.getElementById('webhook-status').innerHTML = `
|
|
All 10 stores have webhooks configured → <code style="color:var(--btc);">http://10.30.20.{210-221}:5000/webhook/btcpay</code><br>
|
|
<span style="color:var(--muted);">Webhooks fire on: InvoiceReceivedPayment, InvoiceProcessing, InvoiceSettled</span><br>
|
|
<span style="color:var(--muted);">Auto-redelivery: enabled on all stores</span>
|
|
`;
|
|
|
|
} catch(e) { console.error(e); }
|
|
}
|
|
|
|
setInterval(() => document.getElementById('clock').textContent = new Date().toLocaleTimeString(), 1000);
|
|
initCharts();
|
|
refresh();
|
|
setInterval(refresh, 8000);
|
|
</script>
|
|
</body>
|
|
</html>
|