18 Commits

Author SHA1 Message Date
f9ffc383a0 fix: add missing statusMap definition and iv-balance reset 2026-07-11 20:27:54 -04:00
d223404a86 fix: remove deprecated iv-status2 element references, clean up status reset logic 2026-07-11 20:24:20 -04:00
b79ee2fdd8 add comprehensive status explanation documentation 2026-07-11 07:57:35 -04:00
131e5ffabf display status using description only (readme doesn't mention period) 2026-07-11 07:56:51 -04:00
ce66b0de3f fix: display status with description in weinfo page, add fallbacks for undefined values, handle empty times table 2026-07-11 07:49:27 -04:00
ff4e46858b add contact field to form and invoice, move submit button, add weinfo link in invoice, fix undefined fields in weinfo page 2026-07-11 06:35:05 -04:00
6c942e58c6 remove Amount and Status rows from invoice display 2026-07-10 11:50:14 -04:00
616c0d1285 add fullscreen QR overlay on click 2026-07-10 10:38:24 -04:00
03d41dd25d include amount in bitcoin URI for QR code and share link 2026-07-10 10:36:01 -04:00
adcd4cde94 fix qr code overflow: remove fixed height on container, pass size to QRCode lib, add max-width to canvas 2026-07-10 10:34:55 -04:00
8bdef5d18f add share invoice button with Web Share API / clipboard fallback 2026-07-10 10:32:52 -04:00
5536d8b14e rename 'Minimum amount' to 'Annual fee' in invoice 2026-07-10 10:30:05 -04:00
6cfa521adc rename Minimum amount -> Fee annuale 2026-07-10 10:29:15 -04:00
560a4d1cbe move note inside invoice div to prevent overlap with qrcode on mobile 2026-07-10 10:26:06 -04:00
221bfec74e add Amount field to invoice 2026-07-10 10:20:22 -04:00
c9749bfed1 change sort order: height desc, points desc, wins desc, fee asc, version desc 2026-07-10 10:16:43 -04:00
756ab98c41 remove Balance column from server list 2026-07-10 10:12:27 -04:00
098ca48e9d rewrite terms for yearly-listing model
author: donkey-ai
2026-07-10 10:09:47 -04:00
3 changed files with 298 additions and 61 deletions

153
README.md
View File

@@ -1,2 +1,153 @@
welist html
# Server Status: Complete Explanation and Reference Guide
This page explains all the **Will Executor server health statuses** used throughout the BAL Web Interface. Each status type provides critical information about server operation, health checks, and potential issues for administrators monitoring the Bitcoin After Life network.
## Status Code Overview
| Status | Description | When It Occurs |
|--------|-------------|----------------|
| `OK` | Health check passed | Server is online, functioning normally |
| `TIMEOUT: <message>` | Connection timeout | Server response took too long |
| `CONNECTION: <message>` | Connection error | Network connectivity issues |
| `STATUS: <code>` | HTTP error (from server) | Server returned non-200 status |
| `WRONG REPLY: <message>` | Invalid JSON response | Server response couldn't be parsed |
| `REQUEST: <message>` | Request error | Client-side request failed |
| `BODY: <message>` | Body read error | Failed to read response body |
| `DECODE: <message>` | Decode error | Failed to decode response content |
| `KO` | Generic error | Unknown or unspecified error |
## Status Types Explained
### 🟢 Good Statuses
**`OK`** - Everything is functioning correctly:
- Server is online and responding
- Health check completed successfully
- Ready for new registration/processing
### 🟡 Warning/Moderate Issues
**`TIMEOUT: <message>`** - Connection timed out:
- Server took too than expected to respond
- Could indicate server overload or network issues
- Requires attention but server may still be functional
**`CONNECTION: <message>`** - Connection error:
- Network connectivity problems
- Server might be reachable but cannot communicate
- Potential infrastructure or configuration issue
**`STATUS: <code>`** - HTTP error:
- Server responded with an error code (4xx or 5xx)
- Could indicate misconfiguration, maintenance, or issues |
- Different error codes mean different things
### 🔴 Critical Issues
**`WRONG REPLY`** - Invalid JSON response:
- Server response format is incorrect
- May indicate server software bug or corruption
- Prevents proper data processing
**`REQUEST`** - Request error:
- Client request failed
- Network or protocol issue
- Server may be unreachable
**`BODY`** - Body read error:
- Failed to read response body after successful connection
- Could indicate server processing issues
- Data corruption or transmission problems
**`DECODE`** - Decode error:
- Failed to decode response content
- Encoding mismatch or corrupted data
- Prevents proper data interpretation
**`KO`** - Generic error:
- Unknown or unspecified error
- Fallback for all other unidentified issues
- Indicates something went wrong but exact cause unknown
## Example Status Messages
### Normal Operations
```
Status: OK
Meaning: Health check passed
Display: Server is healthy and ready
```
### Network/Time Issues
```
Status: TIMEOUT: Connection timed out after 30 seconds
Meaning: Server took too long to respond
Display: "Connection timeout (Connection timed out after 30 seconds)"
```
### HTTP Errors
```
Status: STATUS: 500
Meaning: Server returned HTTP 500 Internal Server Error
Display: "HTTP error (HTTP error)"
```
### Response Processing Issues
```
Status: WRONG REPLY: Invalid JSON
Meaning: Server response could not be parsed as JSON
Display: "Invalid JSON response (Invalid JSON)"
```
## Practical Usage Guide
### For Server Administrators
- **OK**: Check server resources and logs if needed
- **TIMEOUT/CONNECTION**: Check server network connectivity
- **STATUS**: Review server logs and restart if needed
- **WRONG REPLY/DECODE/REQUEST**: Investigate server software issues
- **KO**: Review server errors and restart
### For Users
- **OK**: Server is working correctly
- **TIMEOUT/CONNECTION**: Temporary issue, try again later
- **STATUS**: Server may be down for maintenance
- **Other errors**: Server issue, contact support if persistent
## Monitoring Tips
1. **Continuous Monitoring**: Regular checks of status codes help detect issues early
2. **Alert Setup**: Configure alerts for non-OK statuses
3. **Log Analysis**: Correlate status codes with server logs
4. **Capacity Planning**: Address persistent timeouts/connections with infrastructure improvements
## Status Processing Logic
### Status Code Processing
```javascript
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'
};
// Extract base status from status message
const st = (data.status||'').split(':')[0];
// Display only the description (e.g., "Connection timeout" instead of "TIMEOUT: Connection timeout")
statusDisplay = statusMap[st] || '—';
```
## Related Documentation
For more details on server setup, monitoring, and troubleshooting, refer to:
- [Server Administration Guide](./administration)
- [Status Monitoring Setup](./monitoring-setup)
- [Error Resolution Handbook](./error-resolve)
## Additional Information
This status system provides a clear, human-readable way to understand server health at a glance, making it easier for administrators to quickly identify and respond to issues.

View File

@@ -139,6 +139,11 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
.note{font-size:13px;color:var(--muted);margin-top:16px;line-height:1.55}
.note strong{color:var(--ink)}
.btn-share{
display:block;margin:0 auto 8px;font:inherit;font-size:13px;cursor:pointer;
padding:8px 16px;border-radius:8px;border:1px solid var(--line-strong);
background:var(--panel);color:var(--accent);
}
.invoice{display:none}
.invoice.show{display:block}
@@ -146,8 +151,18 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
.invoice .row:last-child{border-bottom:0}
.invoice .row span:first-child{color:var(--muted)}
.invoice .row span:last-child{font-family:var(--mono);text-align:right;word-break:break-all}
#qrcode{margin:18px auto 4px;width:160px;height:160px}
#qrcode img{width:160px;height:160px}
.invoice .row a{color:var(--accent);text-decoration:none}
.invoice .row a:hover{text-decoration:underline}
#qrcode{margin:18px auto 4px;width:160px}
#qrcode canvas,#qrcode img{width:160px!important;height:160px!important;max-width:100%}
#qr-overlay{
display:none;position:fixed;inset:0;z-index:999;
background:rgba(0,0,0,.7);cursor:pointer;
align-items:center;justify-content:center;
}
#qr-overlay.show{display:flex}
#qr-big{padding:24px;background:#fff;border-radius:12px}
#qr-big canvas,#qr-big img{width:280px!important;height:280px!important;max-width:90vw}
.ok-line{color:var(--ok);font-weight:600;font-size:14px;margin-bottom:14px}
.err-line{color:#d32f2f;font-weight:600;font-size:14px;margin-bottom:14px}
.terms-box{display:none;margin:16px 0;padding:20px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius)}
@@ -252,7 +267,6 @@ footer a{color:var(--accent);text-decoration:none}
<th>Info</th>
<th class="num">Fee</th>
<th>Version</th>
<th class="num">Balance</th>
<th class="num">Height</th>
<th class="num">Wins</th>
<th class="num">Score</th>
@@ -274,9 +288,12 @@ footer a{color:var(--accent);text-decoration:none}
<h2>Add your Will Executor</h2>
<p class="lead">Enter your server URL. It will be checked online before it appears in the list.</p>
<div class="form-row">
<input type="text" id="url" placeholder="https://bitcoin-after.life:9137">
<button class="btn" id="submit">Add server</button>
<input type="text" id="url" placeholder="https://we.bitcoin-after.life">
</div>
<div style="margin-top:8px">
<input type="text" id="contact" placeholder="Email, npub or Telegram (optional)" style="width:100%;font:inherit;font-size:14px;padding:9px 12px;border:1px solid var(--line-strong);border-radius:8px;background:var(--panel);color:var(--ink)">
</div>
<button class="btn" id="submit" style="margin-top:8px">Add server</button>
<div class="ok-line" id="iv-status"></div>
@@ -294,20 +311,23 @@ footer a{color:var(--accent);text-decoration:none}
</div>
<div class="invoice" id="invoice">
<div class="row"><span>Minimum amount</span><span id="iv-min"></span></div>
<div class="row"><span>Annual fee</span><span id="iv-min"></span></div>
<div class="row"><span>Chain</span><span id="iv-chain"></span></div>
<div class="row"><span>Address</span><span id="iv-addr"></span></div>
<div class="row"><span>Balance</span><span id="iv-balance"></span></div>
<div class="row"><span>Status</span><span id="iv-status2"></span></div>
<div class="row"><span>Status</span><span id="iv-status"></span></div>
<div class="row"><span>Contact</span><span id="iv-contact"></span></div>
<div class="row"><span>Server</span><span id="iv-url"></span></div>
<div class="row"><span>Will Executor</span><span><a id="iv-weinfo" href="#" target="_blank"></a></span></div>
<div id="qrcode"></div>
</div>
<button class="btn-share" id="share-btn">Share invoice</button>
<p class="note">
<strong>Please make sure your server is online before you attempt to add it.</strong>
</p>
</div>
</div>
<div class="panel">
<h2>How ranking works</h2>
<p class="lead">Bid-based ranking: higher contributions rank higher.</p>
@@ -363,6 +383,18 @@ let minAmount = 50000;
const fmt = n => (n != null ? Number(n).toLocaleString('en-US') : '—');
const trunc = (s, m=40) => s && s.length > m ? s.slice(0, m) + '…' : s;
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'
};
function getChain() {
const btn = document.querySelector('#chainSeg [aria-pressed="true"]');
return btn ? btn.dataset.c : 'bitcoin';
@@ -372,7 +404,7 @@ function getFiltered() {
return allServers
.filter(d => withTor ? true : !d.onion)
.filter(d => (d.url + " " + (d.info||"")).toLowerCase().includes(q.toLowerCase()))
.sort((a, b) => (b.balance||0) - (a.balance||0) || (a.base_fee||0) - (b.base_fee||0) || (b.points||0) - (a.points||0));
.sort((a, b) => (b.last_block||0) - (a.last_block||0) || (b.points||0) - (a.points||0) || (b.count_win||0) - (a.count_win||0) || (a.base_fee||0) - (b.base_fee||0) || (b.version||'').localeCompare(a.version||''));
}
function render() {
@@ -382,7 +414,7 @@ function render() {
tb.innerHTML = ""; ml.innerHTML = "";
if (!list.length) {
tb.innerHTML = '<tr><td colspan="9" style="padding:28px;text-align:center;color:var(--muted)">No servers found.</td></tr>';
tb.innerHTML = '<tr><td colspan="8" style="padding:28px;text-align:center;color:var(--muted)">No servers found.</td></tr>';
}
list.forEach((d, i) => {
@@ -396,7 +428,6 @@ function render() {
<td class="info">${DOMPurify.sanitize(d.info||'—')}</td>
<td class="num">${fmt(d.base_fee)}</td>
<td class="mono">${DOMPurify.sanitize(d.version||'—')}</td>
<td class="num">${fmt(d.balance)}</td>
<td class="num">${fmt(d.last_block)}</td>
<td class="num">${d.count_win != null ? d.count_win : '—'}</td>
<td class="num"><span class="score">${fmt(d.points)}</span></td>
@@ -411,7 +442,6 @@ function render() {
<div><span>Score</span><b>${fmt(d.points)}</b></div>
<div><span>Wins</span><b>${d.count_win != null ? d.count_win : '—'}</b></div>
<div><span>Fee</span><b>${fmt(d.base_fee)} sat</b></div>
<div><span>Balance</span><b>${fmt(d.balance)}</b></div>
<div><span>Version</span><b>${DOMPurify.sanitize(d.version||'—')}</b></div>
<div><span>Height</span><b>${fmt(d.last_block)}</b></div>
</div>
@@ -491,9 +521,14 @@ document.getElementById('submit').addEventListener('click', async () => {
document.getElementById('iv-chain').textContent = '—';
document.getElementById('iv-addr').textContent = '—';
document.getElementById('iv-balance').textContent = '—';
document.getElementById('iv-status2').textContent = '—';
document.getElementById('iv-url').textContent = '—';
document.getElementById('iv-contact').textContent = '—';
document.getElementById('iv-weinfo').href = '#';
document.getElementById('iv-weinfo').textContent = '—';
document.getElementById('qrcode').innerHTML = '';
document.getElementById('qrcode').style.cursor = '';
document.getElementById('qr-overlay').classList.remove('show');
document.getElementById('share-btn').textContent = 'Share invoice';
document.getElementById('terms-box').classList.remove('show');
document.getElementById('terms-check').checked = false;
document.getElementById('terms-accept').disabled = true;
@@ -502,7 +537,7 @@ document.getElementById('submit').addEventListener('click', async () => {
const response = await fetch('/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, chain })
body: JSON.stringify({ url, chain, contact: document.getElementById('contact').value.trim() || undefined })
});
const result = await response.json();
if (!response.ok) {
@@ -514,11 +549,48 @@ document.getElementById('submit').addEventListener('click', async () => {
document.getElementById('iv-chain').textContent = result.chain || chain;
document.getElementById('iv-addr').textContent = result.address || '—';
document.getElementById('iv-balance').textContent = result.balance != null ? fmt(result.balance) : '—';
document.getElementById('iv-status2').textContent = result.status || '—';
const invoiceuri = `bitcoin:${result.address}?label=BAL&message=Order+For+url+%26+${encodeURIComponent(url)}`;
const statusVal = result.status || '—';
const st = (statusVal||'').split(':')[0];
document.getElementById('iv-status').textContent = statusMap[st] || '—';
document.getElementById('iv-contact').textContent = document.getElementById('contact').value.trim() || '—';
const weLink = result.id ? `/weinfo/${result.chain || chain}/${result.id}` : null;
const weinfoEl = document.getElementById('iv-weinfo');
if (weLink) {
weinfoEl.href = weLink;
weinfoEl.textContent = 'View page';
} else {
weinfoEl.href = '#';
weinfoEl.textContent = '—';
}
const amountSats = result.amount != null ? result.amount : minAmount;
const amountBtc = (amountSats / 1e8).toFixed(8).replace(/\.?0+$/, '');
const invoiceuri = `bitcoin:${result.address}?amount=${amountBtc}&label=BAL&message=Order+For+url+%26+${encodeURIComponent(url)}`;
const qrEl = document.getElementById('qrcode');
qrEl.innerHTML = '';
new QRCode(qrEl, invoiceuri);
new QRCode(qrEl, { text: invoiceuri, width: 160, height: 160 });
qrEl.style.cursor = 'pointer';
qrEl.onclick = function () {
const overlay = document.getElementById('qr-overlay');
const big = document.getElementById('qr-big');
big.innerHTML = '';
new QRCode(big, { text: invoiceuri, width: 280, height: 280 });
overlay.classList.add('show');
};
document.getElementById('qr-overlay').onclick = function () {
this.classList.remove('show');
};
const shareBtn = document.getElementById('share-btn');
shareBtn.textContent = 'Share invoice';
shareBtn.onclick = function () {
if (navigator.share) {
navigator.share({ title: 'BAL Invoice', text: invoiceuri });
} else {
navigator.clipboard.writeText(invoiceuri).then(() => {
this.textContent = 'Copied!';
setTimeout(() => this.textContent = 'Share invoice', 2000);
});
}
};
const serverUrl = result.url || url;
const infoUrl = serverUrl + '/' + (result.chain || chain) + '/info';
document.getElementById('iv-url').textContent = serverUrl;
@@ -556,5 +628,6 @@ document.getElementById('url').addEventListener('keydown', e => {
fetchMinAmount();
})();
</script>
<div id="qr-overlay"><div id="qr-big"></div></div>
</body>
</html>

View File

@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-<begin▁of▁sentence>8" />
<meta name="viewport" content="width=device-w, initial-scale=1.0" />
<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>
@@ -26,7 +26,7 @@
tr:nth-child(even) {
background-color: #f9f9f9;
}
span{
span {
margin-left:10px;
}
</style>
@@ -81,38 +81,53 @@
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;
document.getElementById('weheight').innerHTML = data.last_block;
document.getElementById('wepoints').innerHTML = data.points;
document.getElementById('wewin').innerHTML = data.count_win;
document.getElementById('wetld').innerHTML = DOMPurify.sanitize(data.tld);
document.getElementById('webalance').innerHTML = data.balance;
document.getElementById('weversion').innerHTML = data.version;;
document.getElementById('westatus').innerHTML = DOMPurify.sanitize(data.status);
document.getElementById('weupdate').innerHTML = data.last_update;
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||'—');
document.getElementById('webalance').innerHTML = data.balance != null ? data.balance : '—';
document.getElementById('weversion').innerHTML = data.version||'—';
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}</td>
<td>${DOMPurify.sanitize(wetime.blockHash)}</td>
<td>${wetime.blockHeight != null ? wetime.blockHeight : '—'}</td>
<td>${DOMPurify.sanitize(wetime.blockHash||'—')}</td>
<td>${d}</td>
<td>${wetime.diff/1000000} s</td>
<td>${wetime.position+1}</td>
<td>${wetime.participants}</td>
<td>${wetime.points}</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.length > 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 ){
@@ -126,8 +141,6 @@
}
getWeInfo();
</script>
</body>
</html>