- 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.
217 lines
9.5 KiB
HTML
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"> </h2>
|
|
<h3 id="weinfo"> </h3>
|
|
<h4 id="wechain"> </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"> </span></div>
|
|
<div><span>Fees:</span><span id="wefees"> </span></div>
|
|
<div><span>Tld:</span><span id="wetld"> </span></div>
|
|
<div><span>Last Update:</span><span id="weupdate"> </span></div>
|
|
</div>
|
|
<div style="float:left;border:1;margin-right:50px;">
|
|
<div><span>Height:</span><span id="weheight"> </span></div>
|
|
<div><span>Points:</span><span id="wepoints"> </span></div>
|
|
<div><span>Win:</span><span id="wewin"> </span></div>
|
|
</div>
|
|
<div style="border:1;margin-right:50px;">
|
|
<div><span>Balance:</span><span id="webalance"> </span></div>
|
|
<div><span>Donations:</span><span id="wedonations"> </span></div>
|
|
<div><span>Status:</span><span id="westatus"> </span></div>
|
|
<div><span>Expiry:</span><span id="weexpiry"> </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> |