1 Commits

Author SHA1 Message Date
0ab7b458a4 Update terms.html
Rewrite sections 2-4: yearly listing model, fixed logo
2026-07-09 13:06:09 +00:00
4 changed files with 109 additions and 390 deletions

153
README.md
View File

@@ -1,153 +1,2 @@
# Server Status: Complete Explanation and Reference Guide welist html
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

@@ -117,8 +117,7 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums} .num{font-family:var(--mono);font-variant-numeric:tabular-nums}
.score{font-family:var(--display);font-weight:600;font-size:15px} .score{font-family:var(--display);font-weight:600;font-size:15px}
.add-grid{display:grid;grid-template-columns:1.3fr 1fr;gap:24px;margin:40px 0;align-items:stretch} .add-grid{display:grid;grid-template-columns:1.3fr 1fr;gap:24px;margin:40px 0;align-items:start}
.add-grid>.panel{display:flex;flex-direction:column}
.panel{ .panel{
background:var(--panel);border:1px solid var(--line); background:var(--panel);border:1px solid var(--line);
border-radius:var(--radius);padding:24px;box-shadow:var(--shadow); border-radius:var(--radius);padding:24px;box-shadow:var(--shadow);
@@ -140,17 +139,6 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none} .btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
.note{font-size:13px;color:var(--muted);margin-top:16px;line-height:1.55} .note{font-size:13px;color:var(--muted);margin-top:16px;line-height:1.55}
.note strong{color:var(--ink)} .note strong{color:var(--ink)}
.freshness-note{
font-size:12px;line-height:1.4;color:var(--muted);
background:var(--accent-soft);border:1px solid var(--line);border-radius:8px;
padding:7px 12px;margin:0 0 12px;
}
.freshness-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{display:none}
.invoice.show{display:block} .invoice.show{display:block}
@@ -158,18 +146,8 @@ tbody tr:hover{background:color-mix(in srgb,var(--accent-soft) 50%,transparent)}
.invoice .row:last-child{border-bottom:0} .invoice .row:last-child{border-bottom:0}
.invoice .row span:first-child{color:var(--muted)} .invoice .row span:first-child{color:var(--muted)}
.invoice .row span:last-child{font-family:var(--mono);text-align:right;word-break:break-all} .invoice .row span:last-child{font-family:var(--mono);text-align:right;word-break:break-all}
.invoice .row a{color:var(--accent);text-decoration:none} #qrcode{margin:18px auto 4px;width:160px;height:160px}
.invoice .row a:hover{text-decoration:underline} #qrcode img{width:160px;height:160px}
#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} .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} .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)} .terms-box{display:none;margin:16px 0;padding:20px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius)}
@@ -265,10 +243,6 @@ footer a{color:var(--accent);text-decoration:none}
<a class="btn-ghost" id="download" href="#" download="willexecutors.json">↓ JSON</a> <a class="btn-ghost" id="download" href="#" download="willexecutors.json">↓ JSON</a>
</div> </div>
<p class="freshness-note">
<strong>Fee/description/version</strong> refresh about once a day (timing varies). <strong>Deposits</strong> are credited instantly after 3 confirmations (~30 min).
</p>
<div class="table-card"> <div class="table-card">
<table id="tbl"> <table id="tbl">
<thead> <thead>
@@ -278,6 +252,7 @@ footer a{color:var(--accent);text-decoration:none}
<th>Info</th> <th>Info</th>
<th class="num">Fee</th> <th class="num">Fee</th>
<th>Version</th> <th>Version</th>
<th class="num">Balance</th>
<th class="num">Height</th> <th class="num">Height</th>
<th class="num">Wins</th> <th class="num">Wins</th>
<th class="num">Score</th> <th class="num">Score</th>
@@ -297,21 +272,18 @@ footer a{color:var(--accent);text-decoration:none}
<div class="add-grid"> <div class="add-grid">
<div class="panel"> <div class="panel">
<h2>Add your Will Executor</h2> <h2>Add your Will Executor</h2>
<p class="lead">Enter your server URL. It will appear in the list once your deposit is confirmed (typically about 30 minutes).</p> <p class="lead">Enter your server URL. It will be checked online before it appears in the list.</p>
<div class="form-row"> <div class="form-row">
<input type="text" id="url" placeholder="https://we.bitcoin-after.life"> <input type="text" id="url" placeholder="https://bitcoin-after.life:9137">
<button class="btn" id="submit">Add server</button>
</div> </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> <div class="ok-line" id="iv-status"></div>
<div class="terms-box" id="terms-box"> <div class="terms-box" id="terms-box">
<p class="lead">You are about to add this server:</p> <p class="lead">You are about to add this server:</p>
<p><a id="terms-url" href="#" target="_blank" rel="noopener" class="mono"></a></p> <p><a id="terms-url" href="#" target="_blank" rel="noopener" class="mono"></a></p>
<p style="font-size:13px;margin-top:4px"><strong>Make sure the link above works correctly before paying. No refunds after payment. URL changes require proof of ownership — see the <a href="/terms.html" target="_blank">Terms</a>.</strong></p> <p style="font-size:13px;margin-top:4px"><strong>Make sure the link above works correctly before paying. No refunds or URL changes after payment.</strong></p>
<p style="font-size:14px"> <p style="font-size:14px">
<label> <label>
<input type="checkbox" id="terms-check"> <input type="checkbox" id="terms-check">
@@ -322,31 +294,26 @@ footer a{color:var(--accent);text-decoration:none}
</div> </div>
<div class="invoice" id="invoice"> <div class="invoice" id="invoice">
<div class="row"><span>Annual fee</span><span id="iv-min"></span></div> <div class="row"><span>Minimum amount</span><span id="iv-min"></span></div>
<div class="row"><span>Chain</span><span id="iv-chain"></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>Address</span><span id="iv-addr"></span></div>
<div class="row"><span>Balance</span><span id="iv-balance"></span></div> <div class="row"><span>Balance</span><span id="iv-balance"></span></div>
<div class="row"><span>Donations</span><span id="iv-donations"></span></div> <div class="row"><span>Status</span><span id="iv-status2"></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>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 class="row" id="iv-nextdep-row" style="display:none"><span>Next renewal</span><span id="iv-nextdep"></span></div>
<div id="qrcode"></div> <div id="qrcode"></div>
<button class="btn-share" id="share-btn">Share invoice</button> </div>
<p class="note"> <p class="note">
<strong>Please make sure your server is online before you attempt to add it.</strong> <strong>Please make sure your server is online before you attempt to add it.</strong>
</p> </p>
</div> </div>
</div>
<div class="panel"> <div class="panel">
<h2>How ranking works</h2> <h2>How ranking works</h2>
<p class="lead">Ordered by excess donated, then total paid, then base fee (lower first), then WeTime points.</p> <p class="lead">Bid-based ranking: higher contributions rank higher.</p>
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Yearly fee</span><b class="mono"><span id="thr-min">50,000</span> sats</b></div> <div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Deposit threshold</span><b class="mono"><span id="thr-min">50,000</span> sats</b></div>
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Listing period</span><b>365 days</b></div> <div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Plugin integration</span><b>automatic</b></div>
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px"><span style="color:var(--muted)">Renewal window</span><b>from 6 months before expiry</b></div> <div style="display:flex;justify-content:space-between;padding:8px 0;font-size:13px"><span style="color:var(--muted)">Early-adopter guarantee</span><b>12 months</b></div>
<div style="display:flex;justify-content:space-between;padding:8px 0;font-size:13px"><span style="color:var(--muted)">Plugin integration</span><b>automatic</b></div>
</div> </div>
</div> </div>
@@ -361,13 +328,13 @@ footer a{color:var(--accent);text-decoration:none}
<div class="info-block"> <div class="info-block">
<span class="ic">02</span> <span class="ic">02</span>
<h3>Ranking System</h3> <h3>Ranking System</h3>
<p>This leaderboard is ordered by excess donated (over the yearly fee), then total amount paid, then lower base fee, then WeTime points.</p> <p>This leaderboard operates on a bid-based ranking system where higher contributions result in better positioning.</p>
</div> </div>
<div class="info-block"> <div class="info-block">
<span class="ic">03</span> <span class="ic">03</span>
<h3>Listing &amp; Renewal</h3> <h3>Early Adopter Incentives</h3>
<p>A flat yearly fee buys 365 days of presence in the list, starting when your payment reaches 3 confirmations. You can renew for one additional year at any time from 6 months before expiry; listings that are not renewed are removed at expiry. See the <a href="/terms.html" target="_blank">Terms</a> for details.</p> <p>To support the project's initial launch phase, we plan to maintain the first Will Executor servers for a minimum of 12 months, rewarding early participants. Subsequently, premium positions will be available through an auction system based on protocol revenue generation.</p>
</div> </div>
<div class="info-block"> <div class="info-block">
@@ -384,7 +351,6 @@ footer a{color:var(--accent);text-decoration:none}
<div class="wrap"> <div class="wrap">
<span>Bitcoin After Life — Will Executor List</span> <span>Bitcoin After Life — Will Executor List</span>
<a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a> <a href="mailto:welist@bitcoin-after.life">welist@bitcoin-after.life</a>
<a href="/terms.html">Terms</a>
<a class="mono" id="commit-link" href="https://bitcoin-after.life/gitea/bitcoinafterlife/bal-welist-website/commit/COMMIT_HASH_PLACEHOLDER">COMMIT_HASH_PLACEHOLDER</a> <a class="mono" id="commit-link" href="https://bitcoin-after.life/gitea/bitcoinafterlife/bal-welist-website/commit/COMMIT_HASH_PLACEHOLDER">COMMIT_HASH_PLACEHOLDER</a>
</div> </div>
</footer> </footer>
@@ -404,7 +370,9 @@ function getChain() {
function getFiltered() { function getFiltered() {
return allServers return allServers
.filter(d => (d.url + " " + (d.info||"")).toLowerCase().includes(q.toLowerCase())); .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));
} }
function render() { function render() {
@@ -414,7 +382,7 @@ function render() {
tb.innerHTML = ""; ml.innerHTML = ""; tb.innerHTML = ""; ml.innerHTML = "";
if (!list.length) { if (!list.length) {
tb.innerHTML = '<tr><td colspan="8" style="padding:28px;text-align:center;color:var(--muted)">No servers found.</td></tr>'; tb.innerHTML = '<tr><td colspan="9" style="padding:28px;text-align:center;color:var(--muted)">No servers found.</td></tr>';
} }
list.forEach((d, i) => { list.forEach((d, i) => {
@@ -428,6 +396,7 @@ function render() {
<td class="info">${DOMPurify.sanitize(d.info||'—')}</td> <td class="info">${DOMPurify.sanitize(d.info||'—')}</td>
<td class="num">${fmt(d.base_fee)}</td> <td class="num">${fmt(d.base_fee)}</td>
<td class="mono">${DOMPurify.sanitize(d.version||'—')}</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">${fmt(d.last_block)}</td>
<td class="num">${d.count_win != null ? d.count_win : '—'}</td> <td class="num">${d.count_win != null ? d.count_win : '—'}</td>
<td class="num"><span class="score">${fmt(d.points)}</span></td> <td class="num"><span class="score">${fmt(d.points)}</span></td>
@@ -442,18 +411,18 @@ function render() {
<div><span>Score</span><b>${fmt(d.points)}</b></div> <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>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>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>Version</span><b>${DOMPurify.sanitize(d.version||'—')}</b></div>
<div><span>Height</span><b>${fmt(d.last_block)}</b></div> <div><span>Height</span><b>${fmt(d.last_block)}</b></div>
</div> </div>
</div>`); </div>`);
}); });
const online = allServers.filter(d => d.status === "OK").length; const online = allServers.filter(d => d.status === "online" || d.status === "OK").length;
document.getElementById('s-online').textContent = online; document.getElementById('s-online').textContent = online;
document.getElementById('s-total').textContent = allServers.length; document.getElementById('s-total').textContent = allServers.length;
// Highest known block across all servers (the list is ranked by fee/points, not by height) const top = getFiltered()[0];
const maxBlock = allServers.reduce((m, d) => Math.max(m, d.last_block || 0), 0); document.getElementById('s-block').textContent = top ? fmt(top.last_block) : '—';
document.getElementById('s-block').textContent = maxBlock ? fmt(maxBlock) : '—';
} }
async function fetchData() { async function fetchData() {
@@ -522,50 +491,18 @@ document.getElementById('submit').addEventListener('click', async () => {
document.getElementById('iv-chain').textContent = '—'; document.getElementById('iv-chain').textContent = '—';
document.getElementById('iv-addr').textContent = '—'; document.getElementById('iv-addr').textContent = '—';
document.getElementById('iv-balance').textContent = '—'; document.getElementById('iv-balance').textContent = '—';
document.getElementById('iv-donations').textContent = '—'; document.getElementById('iv-status2').textContent = '—';
document.getElementById('iv-url').textContent = '—'; document.getElementById('iv-url').textContent = '—';
document.getElementById('iv-contact').textContent = '—';
document.getElementById('iv-weinfo').href = '#';
document.getElementById('iv-weinfo').textContent = '—';
document.getElementById('iv-nextdep-row').style.display = 'none';
document.getElementById('iv-nextdep').textContent = '—';
document.getElementById('iv-min').parentElement.style.display = '';
document.getElementById('iv-addr').parentElement.style.display = '';
document.getElementById('qrcode').style.display = '';
document.getElementById('share-btn').style.display = '';
document.getElementById('qrcode').innerHTML = ''; 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-box').classList.remove('show');
document.getElementById('terms-box').style.display = '';
document.getElementById('terms-check').checked = false; document.getElementById('terms-check').checked = false;
document.getElementById('terms-accept').disabled = true; document.getElementById('terms-accept').disabled = true;
const chain = getChain(); const chain = getChain();
const infoUrl = url + '/' + chain + '/info';
document.getElementById('terms-url').href = infoUrl;
document.getElementById('terms-url').textContent = url;
document.getElementById('terms-box').classList.add('show');
document.getElementById('terms-box').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
});
document.getElementById('terms-check').addEventListener('change', function () {
document.getElementById('terms-accept').disabled = !this.checked;
});
document.getElementById('terms-accept').addEventListener('click', async function () {
document.getElementById('terms-box').classList.remove('show');
const urlInput = document.getElementById('url');
let url = urlInput.value.trim();
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'https://' + url;
}
const chain = getChain();
try { try {
const response = await fetch('/data', { const response = await fetch('/data', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, chain, contact: document.getElementById('contact').value.trim() || undefined }) body: JSON.stringify({ url, chain })
}); });
const result = await response.json(); const result = await response.json();
if (!response.ok) { if (!response.ok) {
@@ -576,86 +513,48 @@ document.getElementById('terms-accept').addEventListener('click', async function
document.getElementById('iv-min').textContent = fmt(result.min_amount != null ? result.min_amount : minAmount) + ' sats'; document.getElementById('iv-min').textContent = fmt(result.min_amount != null ? result.min_amount : minAmount) + ' sats';
document.getElementById('iv-chain').textContent = result.chain || chain; document.getElementById('iv-chain').textContent = result.chain || chain;
document.getElementById('iv-addr').textContent = result.address || '—'; document.getElementById('iv-addr').textContent = result.address || '—';
const balStr = result.balance != null ? fmt(result.balance) : null; document.getElementById('iv-balance').textContent = result.balance != null ? fmt(result.balance) : '—';
const uncStr = result.unconfirmed_balance != null ? fmt(result.unconfirmed_balance) : null; document.getElementById('iv-status2').textContent = result.status || '—';
document.getElementById('iv-balance').textContent = balStr ? (uncStr ? balStr + ' (' + uncStr + ')' : balStr) : '—'; const invoiceuri = `bitcoin:${result.address}?label=BAL&message=Order+For+url+%26+${encodeURIComponent(url)}`;
document.getElementById('iv-donations').textContent = result.donation != null ? fmt(result.donation) : '—';
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 serverUrl = result.url || url;
document.getElementById('iv-url').textContent = serverUrl;
if (result.payment_needed === false) {
document.getElementById('iv-min').parentElement.style.display = 'none';
document.getElementById('iv-addr').parentElement.style.display = 'none';
document.getElementById('qrcode').style.display = 'none';
document.getElementById('share-btn').style.display = 'none';
document.getElementById('iv-nextdep').textContent = result.next_deposit_date || '—';
document.getElementById('iv-nextdep-row').style.display = 'flex';
document.getElementById('iv-status').className = 'ok-line';
document.getElementById('iv-status').textContent = 'Server is already listed. Next renewal: ' + (result.next_deposit_date || '—');
const inv = document.getElementById('invoice');
inv.classList.add('show');
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
return;
}
const amountSats = result.min_amount != null ? result.min_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'); const qrEl = document.getElementById('qrcode');
qrEl.innerHTML = ''; qrEl.innerHTML = '';
new QRCode(qrEl, { text: invoiceuri, width: 160, height: 160 }); new QRCode(qrEl, invoiceuri);
qrEl.style.cursor = 'pointer'; const serverUrl = result.url || url;
qrEl.onclick = function () { const infoUrl = serverUrl + '/' + (result.chain || chain) + '/info';
const overlay = document.getElementById('qr-overlay'); document.getElementById('iv-url').textContent = serverUrl;
const big = document.getElementById('qr-big'); document.getElementById('terms-url').href = infoUrl;
big.innerHTML = ''; document.getElementById('terms-url').textContent = serverUrl;
new QRCode(big, { text: invoiceuri, width: 280, height: 280 }); document.getElementById('terms-box').classList.add('show');
overlay.classList.add('show'); document.getElementById('terms-box').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
};
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);
});
}
};
document.getElementById('iv-status').className = 'ok-line';
document.getElementById('iv-status').textContent = '✓ Server verified: ' + serverUrl + '. Pay the invoice — it will be listed after 3 confirmations.';
const inv = document.getElementById('invoice');
inv.classList.add('show');
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} catch (err) { } catch (err) {
document.getElementById('iv-status').className = 'err-line'; document.getElementById('iv-status').className = 'err-line';
document.getElementById('iv-status').textContent = 'Network error. Please try again.'; document.getElementById('iv-status').textContent = 'Network error. Please try again.';
} }
}); });
document.getElementById('terms-check').addEventListener('change', function () {
document.getElementById('terms-accept').disabled = !this.checked;
});
document.getElementById('terms-accept').addEventListener('click', function () {
document.getElementById('terms-box').classList.remove('show');
const inv = document.getElementById('invoice');
document.getElementById('iv-status').className = 'ok-line';
document.getElementById('iv-status').textContent = '✓ Server added: ' + document.getElementById('iv-url').textContent + '. Pay the invoice to complete.';
inv.classList.add('show');
inv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
});
document.getElementById('url').addEventListener('keydown', e => { document.getElementById('url').addEventListener('keydown', e => {
if (e.key === 'Enter') document.getElementById('submit').click(); if (e.key === 'Enter') document.getElementById('submit').click();
}); });
(async function init() { (function init() {
await Promise.all([fetchData(), fetchMinAmount()]);
const blob = new Blob([JSON.stringify(allServers, null, 2)], { type: 'application/json' }); const blob = new Blob([JSON.stringify(allServers, null, 2)], { type: 'application/json' });
document.getElementById('download').href = URL.createObjectURL(blob); document.getElementById('download').href = URL.createObjectURL(blob);
fetchData();
fetchMinAmount();
})(); })();
</script> </script>
<div id="qr-overlay"><div id="qr-big"></div></div>
</body> </body>
</html> </html>

View File

@@ -7,12 +7,9 @@
<link rel="icon" href="Logo_nero.ico" type="image/x-icon"> <link rel="icon" href="Logo_nero.ico" type="image/x-icon">
<!-- <!--
Sections 2-4 rewritten on 2026-07 for the yearly-listing model Sections 2-4 rewritten on 2026-07 for the yearly-listing model
(flat yearly fee, renewal from 6 months before expiry). The list (flat yearly fee, renewal from 6 months before expiry, list
is ordered by total excess donated, then total amount paid, then ordered by points). Section 1, header, footer and the dynamic
base fee, then WeTime points as a tie-breaker (see Section 3), min-deposit script are unchanged from the previous version.
matching the server-side get_list() ordering. Section 1, header,
footer and the dynamic min-deposit script are unchanged from the
previous version.
--> -->
<style> <style>
:root{ :root{
@@ -238,7 +235,7 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
<section class="info-section"> <section class="info-section">
<h2>2. Listing &amp; Renewal</h2> <h2>2. Listing &amp; Renewal</h2>
<p class="sub">A listing works like a hosting plan: a flat yearly fee buys your server one year of presence in the list. Your paid amount accumulates and never decays — it is preserved across renewals and also determines your position in the list (see Section&nbsp;3). Payments are non-refundable (see Section&nbsp;1) — double-check your server URL before paying.</p> <p class="sub">A listing works like a hosting plan: a flat yearly fee buys your server one year of presence in the list. No balances, no ranking by deposit, no daily decay. Payments are non-refundable (see Section&nbsp;1) — double-check your server URL before paying.</p>
<div class="info-grid"> <div class="info-grid">
<div class="info-block"> <div class="info-block">
<span class="ic">01 · PAYMENT</span> <span class="ic">01 · PAYMENT</span>
@@ -255,7 +252,7 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
<div class="info-block"> <div class="info-block">
<span class="ic">03 · CAPACITY</span> <span class="ic">03 · CAPACITY</span>
<h3>No Cap on Listings</h3> <h3>No Cap on Listings</h3>
<p>There is no fixed number of slots: every server with remaining listing time is shown. Paying buys presence in the list; the total amount paid also affects your position in it (see Section&nbsp;3).</p> <p>There is no fixed number of slots: every server with remaining listing time is shown. Paying buys presence in the list, not a position in it (see Section&nbsp;3).</p>
<p>Removal from the list does not affect users who already have your server in their plugin: the plugin's server list is add-only. Being listed matters for reaching new users.</p> <p>Removal from the list does not affect users who already have your server in their plugin: the plugin's server list is add-only. Being listed matters for reaching new users.</p>
</div> </div>
<div class="info-block"> <div class="info-block">
@@ -286,19 +283,13 @@ l-49 -25 -880 0 -880 0 -3 704 c-2 557 0 706 10 713 7 4 407 7 888 5 818 -2
<section class="info-section"> <section class="info-section">
<h2>3. List Order &amp; Server Page</h2> <h2>3. List Order &amp; Server Page</h2>
<p class="sub">The list is ordered by four criteria: first by total excess donated (higher first), then by total amount paid in balance (higher first), then by the base fee charged (lower first), and finally by WeTime points (higher first) as a tie-breaker.</p> <p class="sub">The list is ordered by points, earned by proving your server is online and synchronized. Payment does not affect position.</p>
<div class="info-grid"> <div class="info-grid">
<div class="info-block"> <div class="info-block">
<span class="ic">ORDER</span> <span class="ic">ORDER</span>
<h3>How Position Is Determined</h3> <h3>Points &amp; WeTime</h3>
<p>Servers are sorted by four criteria, in order: first by the total excess donated (higher first), then by the total paid amount (higher first), then by the base fee they charge (lower first), and finally by WeTime points (higher first) to break any remaining ties.</p>
<p>Because excess donated comes first and never decays, over-paying or tipping more — on top of any payment — directly and permanently moves a server up the list. Paying more than the exact yearly fee is the fastest way to improve position.</p>
</div>
<div class="info-block">
<span class="ic">POINTS</span>
<h3>WeTime Points</h3>
<p>Points are earned by participating in WeTime rounds. Each executor periodically submits its block hash; the faster the response within the same block, the more points awarded. The win count tracks how many times an executor was the fastest responder.</p> <p>Points are earned by participating in WeTime rounds. Each executor periodically submits its block hash; the faster the response within the same block, the more points awarded. The win count tracks how many times an executor was the fastest responder.</p>
<p>Points are not transferable, not redeemable and have no monetary value: within this list they act only as a tie-breaker. Points measure presence and reactivity — they do not certify how a server will handle inheritance transactions.</p> <p>Points are not transferable, not redeemable and have no monetary value: they exist only to order this list. Points measure presence and reactivity — they do not certify how a server will handle inheritance transactions.</p>
</div> </div>
<div class="info-block"> <div class="info-block">
<span class="ic">DETAILS</span> <span class="ic">DETAILS</span>

View File

@@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-<begin▁of▁sentence>8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-w, initial-scale=1.0" />
<title>Bal Server List</title> <title>Bal Server List</title>
<link rel="icon" href="Logo_nero.ico" type="image/x-icon"> <link rel="icon" href="Logo_nero.ico" type="image/x-icon">
<script src="/qrcode.min.js"></script> <script src="/qrcode.min.js"></script>
@@ -26,7 +26,7 @@
tr:nth-child(even) { tr:nth-child(even) {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
span { span{
margin-left:10px; margin-left:10px;
} }
</style> </style>
@@ -41,7 +41,6 @@
<div><span>Version:</span><span id="weversion">&nbsp;</span></div> <div><span>Version:</span><span id="weversion">&nbsp;</span></div>
<div><span>Fees:</span><span id="wefees">&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>Tld:</span><span id="wetld">&nbsp;</span></div>
<div><span>Last Update:</span><span id="weupdate">&nbsp;</span></div>
</div> </div>
<div style="float:left;border:1;margin-right:50px;"> <div style="float:left;border:1;margin-right:50px;">
<div><span>Height:</span><span id="weheight">&nbsp;</span></div> <div><span>Height:</span><span id="weheight">&nbsp;</span></div>
@@ -50,9 +49,8 @@
</div> </div>
<div style="border:1;margin-right:50px;"> <div style="border:1;margin-right:50px;">
<div><span>Balance:</span><span id="webalance">&nbsp;</span></div> <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>Status:</span><span id="westatus">&nbsp;</span></div>
<div><span>Expiry:</span><span id="weexpiry">&nbsp;</span></div> <div><span>Last Update:</span><span id="weupdate">&nbsp;</span></div>
</div> </div>
<div> <div>
<table id="wetimes"> <table id="wetimes">
@@ -83,58 +81,38 @@
const chain = path[1]; const chain = path[1];
const cdate=new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14); const cdate=new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
fetchDataAndPopulateTable('/weinfodata/'+chain+"/"+we_id, new URLSearchParams(),(data) => { fetchDataAndPopulateTable('/weinfodata/'+chain+"/"+we_id, new URLSearchParams(),(data) => {
document.getElementById('wename').innerHTML = DOMPurify.sanitize(data.url||'—'); document.getElementById('wename').innerHTML = DOMPurify.sanitize(data.url);
document.getElementById('wechain').innerHTML = DOMPurify.sanitize(data.chain||'—'); document.getElementById('wechain').innerHTML = DOMPurify.sanitize(data.chain);
document.getElementById('weinfo').innerHTML = DOMPurify.sanitize(data.info||'—'); document.getElementById('weinfo').innerHTML = DOMPurify.sanitize(data.info);
document.getElementById('wefees').innerHTML = data.base_fee != null ? data.base_fee : '—'; document.getElementById('wefees').innerHTML = data.base_fee;
document.getElementById('weheight').innerHTML = data.last_block != null ? data.last_block : '—'; document.getElementById('weheight').innerHTML = data.last_block;
document.getElementById('wepoints').innerHTML = data.points != null ? data.points : '—'; document.getElementById('wepoints').innerHTML = data.points;
document.getElementById('wewin').innerHTML = data.count_win != null ? data.count_win : '—'; document.getElementById('wewin').innerHTML = data.count_win;
document.getElementById('wetld').innerHTML = DOMPurify.sanitize(data.tld||'—'); document.getElementById('wetld').innerHTML = DOMPurify.sanitize(data.tld);
const bal = data.balance != null ? data.balance : null; document.getElementById('webalance').innerHTML = data.balance;
const unc = data.unconfirmed_balance != null ? data.unconfirmed_balance : null; document.getElementById('weversion').innerHTML = data.version;;
document.getElementById('webalance').innerHTML = bal ? (unc ? bal + ' (' + unc + ')' : bal) : '—'; document.getElementById('westatus').innerHTML = DOMPurify.sanitize(data.status);
document.getElementById('wedonations').innerHTML = data.donation != null ? data.donation : '—'; document.getElementById('weupdate').innerHTML = data.last_update;
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'); const tableBody = document.querySelector('#wetimes tbody');
tableBody.innerHTML =''; tableBody.innerHTML ='';
if (data.times && data.times.length) {
Object.entries(data.times).forEach(([key,wetime]) => { Object.entries(data.times).forEach(([key,wetime]) => {
const row = document.createElement('tr'); const row = document.createElement('tr');
const d = new Date(wetime.time/1000); const d = new Date(wetime.time/1000);
row.innerHTML = ` row.innerHTML = `
<td>${wetime.blockHeight != null ? wetime.blockHeight : '—'}</td> <td>${wetime.blockHeight}</td>
<td>${DOMPurify.sanitize(wetime.blockHash||'—')}</td> <td>${DOMPurify.sanitize(wetime.blockHash)}</td>
<td>${d}</td> <td>${d}</td>
<td>${wetime.diff != null ? wetime.diff/1000000 + ' s' : '—'}</td> <td>${wetime.diff/1000000} s</td>
<td>${wetime.position != null ? wetime.position+1 : '—'}</td> <td>${wetime.position+1}</td>
<td>${wetime.participants != null ? wetime.participants : '—'}</td> <td>${wetime.participants}</td>
<td>${wetime.points != null ? wetime.points : '—'}</td> <td>${wetime.points}</td>
`; `;
tableBody.appendChild(row); 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 prev=0;
var next=0; var next=0;
if (data.times && data.times.length > 0){ if (data.times.length > 0){
prev = data.times[0].blockHeight+data.times.length; prev = data.times[0].blockHeight+data.times.length;
next = data.times[data.times.length-1].blockHeight; next = data.times[data.times.length-1].blockHeight;
if (prev < 0 ){ if (prev < 0 ){
@@ -148,6 +126,8 @@
} }
getWeInfo(); getWeInfo();
</script> </script>
</body> </body>
</html> </html>