auto broadcast pending txns, show net info
This commit is contained in:
@@ -280,15 +280,39 @@
|
||||
|
||||
function renderTx(tx) {
|
||||
const li = document.createElement('li');
|
||||
const amount = Number(tx.amount).toFixed(8);
|
||||
const amount = Math.abs(Number(tx.amount)).toFixed(8);
|
||||
const type = tx.category === 'send' ? 'Sent' : 'Received';
|
||||
const confirmations = tx.confirmations || 0;
|
||||
|
||||
let status, statusClass, tooltip = '';
|
||||
let ageInfo = '';
|
||||
|
||||
if (confirmations === 0) {
|
||||
status = 'Pending';
|
||||
statusClass = 'status-pending';
|
||||
tooltip = 'title="0 confirmations - waiting for network confirmation"';
|
||||
|
||||
// Calculate transaction age
|
||||
const txTime = tx.time || tx.timereceived;
|
||||
if (txTime) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const ageSeconds = now - txTime;
|
||||
const ageMinutes = Math.floor(ageSeconds / 60);
|
||||
const ageHours = Math.floor(ageMinutes / 60);
|
||||
|
||||
let ageText = '';
|
||||
if (ageHours > 0) {
|
||||
ageText = `${ageHours}h ${ageMinutes % 60}m ago`;
|
||||
} else if (ageMinutes > 0) {
|
||||
ageText = `${ageMinutes}m ago`;
|
||||
} else {
|
||||
ageText = `${ageSeconds}s ago`;
|
||||
}
|
||||
|
||||
ageInfo = `<div class="muted" style="font-size: 11px;">Age: ${ageText}</div>`;
|
||||
tooltip = `title="0 confirmations - waiting for network confirmation (${ageText})"`;
|
||||
} else {
|
||||
tooltip = 'title="0 confirmations - waiting for network confirmation"';
|
||||
}
|
||||
} else if (confirmations < 20) {
|
||||
status = `Immature (${confirmations}/20)`;
|
||||
statusClass = 'status-immature';
|
||||
@@ -302,6 +326,7 @@
|
||||
li.innerHTML = `
|
||||
<div><strong>${type}</strong> ${amount} RIN <span class="status-badge ${statusClass}" ${tooltip}>${status}</span></div>
|
||||
<div class="muted">${tx.time ? new Date(tx.time * 1000).toLocaleString() : ''}</div>
|
||||
${ageInfo}
|
||||
<div class="muted"><a href="https://explorer.rin.so/tx/${tx.txid}" target="_blank" class="tx-link">${tx.txid}</a></div>
|
||||
`;
|
||||
return li;
|
||||
@@ -403,55 +428,94 @@
|
||||
}
|
||||
|
||||
async function fetchNetworkInfo() {
|
||||
let res = await fetch('/api/network', { headers: authHeaders() });
|
||||
let data = await res.json();
|
||||
try {
|
||||
let res = await fetch('/api/network', { headers: authHeaders() });
|
||||
let data = await res.json();
|
||||
|
||||
// If token is invalid, refresh and retry
|
||||
if (data.error === 'invalid_token') {
|
||||
await refreshToken();
|
||||
res = await fetch('/api/network', { headers: authHeaders() });
|
||||
data = await res.json();
|
||||
// If token is invalid, refresh and retry
|
||||
if (data.error === 'invalid_token') {
|
||||
await refreshToken();
|
||||
res = await fetch('/api/network', { headers: authHeaders() });
|
||||
data = await res.json();
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
networkInfo.innerHTML = `<div class="muted">Error: ${data.error}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const network = data.network;
|
||||
const blockchain = data.blockchain;
|
||||
|
||||
// Build peers list
|
||||
let peersHtml = '';
|
||||
if (data.peers && data.peers.length > 0) {
|
||||
peersHtml = '<div style="margin-top: 12px;"><div class="muted" style="font-size: 12px; margin-bottom: 8px;">Connected Peers</div><div style="max-height: 200px; overflow-y: auto;">';
|
||||
data.peers.forEach(peer => {
|
||||
const relayStatus = peer.relaytxes ? '✓ Relay' : '✗ No Relay';
|
||||
const syncStatus = peer.synced_blocks === blockchain.blocks ? '✓ Synced' : `${peer.synced_blocks}`;
|
||||
peersHtml += `
|
||||
<div style="padding: 6px; margin-bottom: 4px; background: rgba(9, 19, 30, 0.6); border-radius: 4px; font-size: 11px;">
|
||||
<div style="font-weight: 600;">${peer.addr}</div>
|
||||
<div class="muted">${relayStatus} | ${peer.version} | Ping: ${peer.pingtime}ms | Blocks: ${syncStatus}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
peersHtml += '</div></div>';
|
||||
}
|
||||
|
||||
networkInfo.innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
||||
<div>
|
||||
<div class="muted">Network Connections</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${network.connections}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Mempool Size</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${data.mempool_size}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Block Height</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.blocks}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Chain</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.chain}</div>
|
||||
</div>
|
||||
</div>
|
||||
${peersHtml}
|
||||
<div class="muted" style="font-size: 12px; margin-top: 12px;">
|
||||
Network Active: ${network.networkactive ? 'Yes' : 'No'} |
|
||||
Relay Fee: ${network.relayfee} RIN |
|
||||
Difficulty: ${Number(blockchain.difficulty).toFixed(2)}
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error('Network info fetch failed:', err);
|
||||
networkInfo.innerHTML = `<div class="muted">Error loading network info: ${err.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
networkInfo.innerHTML = `<div class="muted">Error: ${data.error}</div>`;
|
||||
return;
|
||||
async function rebroadcastPending() {
|
||||
try {
|
||||
const res = await fetch('/api/rebroadcast', {
|
||||
method: 'POST',
|
||||
headers: authHeaders(),
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log(`Rebroadcasted ${data.count} pending transactions`);
|
||||
} catch (err) {
|
||||
console.error('Rebroadcast failed:', err);
|
||||
}
|
||||
|
||||
const network = data.network;
|
||||
const blockchain = data.blockchain;
|
||||
|
||||
networkInfo.innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
||||
<div>
|
||||
<div class="muted">Network Connections</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${network.connections}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Mempool Size</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${data.mempool_size}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Block Height</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.blocks}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Chain</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.chain}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="muted" style="font-size: 12px;">
|
||||
Network Active: ${network.networkactive ? 'Yes' : 'No'} |
|
||||
Relay Fee: ${network.relayfee} RIN |
|
||||
Difficulty: ${blockchain.difficulty.toFixed(2)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
await Promise.all([fetchBalances(), fetchTransactions(), fetchNetworkInfo()]);
|
||||
}
|
||||
|
||||
// Auto-rebroadcast pending transactions every minute
|
||||
setInterval(rebroadcastPending, 60000);
|
||||
|
||||
document.getElementById('refreshButton').addEventListener('click', refresh);
|
||||
document.getElementById('generateButton').addEventListener('click', generateAddress);
|
||||
document.getElementById('sendButton').addEventListener('click', sendCoins);
|
||||
|
||||
Reference in New Issue
Block a user