reorder txns newest on top; show confirmations tooltip

This commit is contained in:
Dobromir Popov
2025-09-30 01:20:40 +03:00
parent 584e2b40c5
commit b96778196c
2 changed files with 99 additions and 4 deletions

View File

@@ -131,6 +131,34 @@ def list_transactions():
return jsonify({"error": str(exc)}), 500 return jsonify({"error": str(exc)}), 500
@app.route("/api/network", methods=["GET"])
@require_token
def get_network_info():
try:
network_info = rpc_call("getnetworkinfo")
peer_info = rpc_call("getpeerinfo")
mempool = rpc_call("getrawmempool")
blockchain_info = rpc_call("getblockchaininfo")
return jsonify({
"network": {
"connections": network_info.get("connections", 0),
"networkactive": network_info.get("networkactive", False),
"relayfee": network_info.get("relayfee", 0),
},
"peers": len(peer_info),
"mempool_size": len(mempool),
"blockchain": {
"blocks": blockchain_info.get("blocks", 0),
"headers": blockchain_info.get("headers", 0),
"difficulty": blockchain_info.get("difficulty", 0),
"chain": blockchain_info.get("chain", "unknown"),
}
})
except RuntimeError as exc:
return jsonify({"error": str(exc)}), 500
@app.route("/") @app.route("/")
def root(): def root():
return send_from_directory(app.static_folder, "index.html") return send_from_directory(app.static_folder, "index.html")

View File

@@ -147,6 +147,14 @@
background: rgba(255, 152, 0, 0.2); background: rgba(255, 152, 0, 0.2);
color: #ff9800; color: #ff9800;
} }
.tx-link {
color: #29b6f6;
text-decoration: none;
word-break: break-all;
}
.tx-link:hover {
text-decoration: underline;
}
@media (max-width: 900px) { @media (max-width: 900px) {
body { body {
flex-direction: column; flex-direction: column;
@@ -207,6 +215,13 @@
<h2>Recent Activity</h2> <h2>Recent Activity</h2>
<ul class="transaction-list" id="txList"></ul> <ul class="transaction-list" id="txList"></ul>
</div> </div>
<div class="card">
<h2>Network Status</h2>
<div id="networkInfo">
<div class="muted">Loading network information...</div>
</div>
</div>
</main> </main>
<script> <script>
@@ -217,6 +232,7 @@
const sendStatus = document.getElementById('sendStatus'); const sendStatus = document.getElementById('sendStatus');
const generatedAddressCard = document.getElementById('generatedAddressCard'); const generatedAddressCard = document.getElementById('generatedAddressCard');
const generatedAddress = document.getElementById('generatedAddress'); const generatedAddress = document.getElementById('generatedAddress');
const networkInfo = document.getElementById('networkInfo');
let apiToken = localStorage.getItem('rinWebWalletToken'); let apiToken = localStorage.getItem('rinWebWalletToken');
@@ -268,22 +284,25 @@
const type = tx.category === 'send' ? 'Sent' : 'Received'; const type = tx.category === 'send' ? 'Sent' : 'Received';
const confirmations = tx.confirmations || 0; const confirmations = tx.confirmations || 0;
let status, statusClass; let status, statusClass, tooltip = '';
if (confirmations === 0) { if (confirmations === 0) {
status = 'Pending'; status = 'Pending';
statusClass = 'status-pending'; statusClass = 'status-pending';
tooltip = 'title="0 confirmations - waiting for network confirmation"';
} else if (confirmations < 20) { } else if (confirmations < 20) {
status = `Immature (${confirmations}/20)`; status = `Immature (${confirmations}/20)`;
statusClass = 'status-immature'; statusClass = 'status-immature';
tooltip = `title="${confirmations} confirmations - needs 20 for full confirmation"`;
} else { } else {
status = 'Confirmed'; status = 'Confirmed';
statusClass = 'status-confirmed'; statusClass = 'status-confirmed';
tooltip = `title="${confirmations} confirmations - fully confirmed"`;
} }
li.innerHTML = ` li.innerHTML = `
<div><strong>${type}</strong> ${amount} RIN <span class="status-badge ${statusClass}">${status}</span></div> <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> <div class="muted">${tx.time ? new Date(tx.time * 1000).toLocaleString() : ''}</div>
<div class="muted">${tx.txid}</div> <div class="muted"><a href="https://explorer.rin.so/tx/${tx.txid}" target="_blank" class="tx-link">${tx.txid}</a></div>
`; `;
return li; return li;
} }
@@ -308,6 +327,8 @@
txList.innerHTML = '<li class="muted">No transactions yet.</li>'; txList.innerHTML = '<li class="muted">No transactions yet.</li>';
return; return;
} }
// Sort transactions by time (newest first)
data.transactions.sort((a, b) => (b.time || 0) - (a.time || 0));
data.transactions.forEach((tx) => txList.appendChild(renderTx(tx))); data.transactions.forEach((tx) => txList.appendChild(renderTx(tx)));
} }
@@ -381,8 +402,54 @@
await refresh(); await refresh();
} }
async function fetchNetworkInfo() {
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 (data.error) {
networkInfo.innerHTML = `<div class="muted">Error: ${data.error}</div>`;
return;
}
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() { async function refresh() {
await Promise.all([fetchBalances(), fetchTransactions()]); await Promise.all([fetchBalances(), fetchTransactions(), fetchNetworkInfo()]);
} }
document.getElementById('refreshButton').addEventListener('click', refresh); document.getElementById('refreshButton').addEventListener('click', refresh);