Files
bal-welist-website/weinfo.html
bitcoinafterlife 956e133b7c Show deposit progress after the invoice
- The invoice checks the server every 30 seconds and shows the amount
  detected, then the confirmation once the server is listed.
- The server page gets the same banner, with a 24-hour limit on checks.
- The invoice link is now "Follow your payment", so the invoice can be
  closed and the deposit followed from a fixed address.
2026-11-01 12:36:46 +01:00

217 lines
9.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bal Server List</title>
<link rel="icon" href="Logo_nero.ico" type="image/x-icon">
<script src="/qrcode.min.js"></script>
<script src="/purify.min.js"></script>
<script src="/bal.js"></script>
</head>
<style>
table {
border-collapse: collapse;
width: 100%;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
span {
margin-left:10px;
}
</style>
<body>
<div align="center">
<a href="/"><img alt="BAL LOGO" src="/Logo_nero.png"></a>
<h2 id="wename">&nbsp;</h2>
<h3 id="weinfo">&nbsp;</h3>
<h4 id="wechain">&nbsp;</h4>
<div id="wepayment" style="display:none;max-width:640px;margin:10px auto 18px;padding:10px 14px;border-radius:8px;font-weight:600"></div>
</div>
<div style="float:left;border:1;margin-right:50px;">
<div><span>Version:</span><span id="weversion">&nbsp;</span></div>
<div><span>Fees:</span><span id="wefees">&nbsp;</span></div>
<div><span>Tld:</span><span id="wetld">&nbsp;</span></div>
<div><span>Last Update:</span><span id="weupdate">&nbsp;</span></div>
</div>
<div style="float:left;border:1;margin-right:50px;">
<div><span>Height:</span><span id="weheight">&nbsp;</span></div>
<div><span>Points:</span><span id="wepoints">&nbsp;</span></div>
<div><span>Win:</span><span id="wewin">&nbsp;</span></div>
</div>
<div style="border:1;margin-right:50px;">
<div><span>Balance:</span><span id="webalance">&nbsp;</span></div>
<div><span>Donations:</span><span id="wedonations">&nbsp;</span></div>
<div><span>Status:</span><span id="westatus">&nbsp;</span></div>
<div><span>Expiry:</span><span id="weexpiry">&nbsp;</span></div>
</div>
<div>
<table id="wetimes">
<thead>
<tr>
<th colspan=5><span id="chainth"><!-- --></span>: <span id="paginator"></span></th>
</tr>
<tr>
<th>height</th>
<th>block hash</th>
<th>time</th>
<th>diff</th>
<th>position</th>
<th>participants</th>
<th>points</th>
</tr>
</thead>
<tbody>
<!-- -->
</tbody>
</table>
</div>
<a href="" id="download">Download</a>
<script type="text/javascript">
// Payment banner: this page has a fixed address, so whoever paid can close the
// invoice and follow the deposit here. It asks the server data every 30 seconds
// until the server is listed. The status only moves forward, because
// unconfirmed_balance may go back to 0 before the deposit is credited.
const PAYMENT_POLL_MS = 30000;
const PAYMENT_POLL_MAX_MS = 24 * 60 * 60 * 1000;
const paymentStarted = Date.now();
let paymentTimer = null, paymentSeen = false, paymentAmount = 0, firstExpiry;
function showPayment(text, ok) {
const box = document.getElementById('wepayment');
box.textContent = text;
box.style.display = 'block';
box.style.color = ok ? '#14703a' : '#7a5200';
box.style.background = ok ? '#e6f6ec' : '#fdf3dc';
}
function stopPayment() {
if (paymentTimer) clearInterval(paymentTimer);
paymentTimer = null;
}
async function pollPayment() {
if (Date.now() - paymentStarted > PAYMENT_POLL_MAX_MS) return stopPayment();
const path = window.location.pathname.split('/').filter(p => p !== '');
let data;
try {
const response = await fetch('/weinfodata/' + path[1] + '/' + path[2]);
if (!response.ok) return;
data = await response.json();
} catch (err) {
return; // temporary network problem: try again at the next round
}
if (!data) return;
const expiry = data.expiry_date || null;
if (firstExpiry === undefined) firstExpiry = expiry;
const unconfirmed = Number(data.unconfirmed_balance) > 0;
const active = expiry && new Date(expiry.replace(' ', 'T') + 'Z') > new Date();
const bal = data.balance != null ? data.balance : null;
const unc = data.unconfirmed_balance != null ? data.unconfirmed_balance : null;
document.getElementById('webalance').textContent = bal ? (unc ? bal + ' (' + unc + ')' : bal) : '—';
const expiryCell = document.createElement('b');
expiryCell.textContent = expiry || '—';
document.getElementById('weexpiry').replaceChildren(expiryCell);
if (expiry && expiry !== firstExpiry) {
showPayment('✓ Payment confirmed: this server is now listed.', true);
stopPayment();
} else if (unconfirmed || paymentSeen) {
paymentSeen = true;
if (unconfirmed) paymentAmount = Number(data.unconfirmed_balance);
showPayment('Payment detected: ' + paymentAmount.toLocaleString('en-US') + ' sats, waiting for 3 confirmations (about 30 minutes). This page updates by itself.', false);
} else if (active) {
stopPayment(); // already listed and nothing incoming: no banner, nothing to follow
} else {
showPayment('Waiting for payment. This page updates by itself.', false);
}
}
document.addEventListener('DOMContentLoaded', () => {
paymentTimer = setInterval(pollPayment, PAYMENT_POLL_MS);
pollPayment();
});
function getWeInfo(){
const path = window.location.pathname.split('/').filter(p => p !== '');
const we_id = path[2];
const chain = path[1];
const cdate=new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
fetchDataAndPopulateTable('/weinfodata/'+chain+"/"+we_id, new URLSearchParams(),(data) => {
document.getElementById('wename').innerHTML = DOMPurify.sanitize(data.url||'—');
document.getElementById('wechain').innerHTML = DOMPurify.sanitize(data.chain||'—');
document.getElementById('weinfo').innerHTML = DOMPurify.sanitize(data.info||'—');
document.getElementById('wefees').innerHTML = data.base_fee != null ? data.base_fee : '—';
document.getElementById('weheight').innerHTML = data.last_block != null ? data.last_block : '—';
document.getElementById('wepoints').innerHTML = data.points != null ? data.points : '—';
document.getElementById('wewin').innerHTML = data.count_win != null ? data.count_win : '—';
document.getElementById('wetld').innerHTML = DOMPurify.sanitize(data.tld||'—');
const bal = data.balance != null ? data.balance : null;
const unc = data.unconfirmed_balance != null ? data.unconfirmed_balance : null;
document.getElementById('webalance').innerHTML = bal ? (unc ? bal + ' (' + unc + ')' : bal) : '—';
document.getElementById('wedonations').innerHTML = data.donation != null ? data.donation : '—';
document.getElementById('weversion').innerHTML = data.version||'—';
document.getElementById('weexpiry').innerHTML = '<b>' + (data.expiry_date || '—') + '</b>';
document.getElementById('weupdate').innerHTML = data.last_update || '—';
const statusMap = {
'OK': 'Health check passed',
'TIMEOUT': 'Connection timeout',
'CONNECTION': 'Connection error',
'STATUS': 'HTTP error',
'WRONG REPLY': 'Invalid JSON response',
'REQUEST': 'Request error',
'BODY': 'Body read error',
'DECODE': 'Decode error',
'KO': 'Generic error'
};
const st = (data.status||'').split(':')[0];
document.getElementById('westatus').innerHTML = statusMap[st] || '—';
const tableBody = document.querySelector('#wetimes tbody');
tableBody.innerHTML ='';
if (data.times && data.times.length) {
Object.entries(data.times).forEach(([key,wetime]) => {
const row = document.createElement('tr');
const d = new Date(wetime.time/1000);
row.innerHTML = `
<td>${wetime.blockHeight != null ? wetime.blockHeight : '—'}</td>
<td>${DOMPurify.sanitize(wetime.blockHash||'—')}</td>
<td>${d}</td>
<td>${wetime.diff != null ? wetime.diff/1000000 + ' s' : '—'}</td>
<td>${wetime.position != null ? wetime.position+1 : '—'}</td>
<td>${wetime.participants != null ? wetime.participants : '—'}</td>
<td>${wetime.points != null ? wetime.points : '—'}</td>
`;
tableBody.appendChild(row);
});
} else {
tableBody.innerHTML = '<tr><td colspan="7" style="padding:20px;text-align:center;color:#999">No rounds yet.</td></tr>';
}
var prev=0;
var next=0;
if (data.times && data.times.length > 0){
prev = data.times[0].blockHeight+data.times.length;
next = data.times[data.times.length-1].blockHeight;
if (prev < 0 ){
prev = 0;
}
}
const paginator = {prev:prev,next:next};
return paginator;
},{elementName:"download",fileName:"weinfo_"+cdate+".json",linkText:"download"})
}
getWeInfo();
</script>
</body>
</html>